Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve BSTR Handling #634

Merged
merged 1 commit into from
Apr 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Bug Fixes
* [#601](https://github.com/java-native-access/jna/pull/601): Remove COMThread and COM initialization from objects and require callers to initialize COM themselves. Asserts are added to guard correct usage. - [@matthiasblaesing](https://github.com/matthiasblaesing).
* [#602](https://github.com/java-native-access/jna/pull/602): Make sure SID related memory is properly released once no longer required [@lgoldstein](https://github.com/lgoldstein).
* [#610](https://github.com/java-native-access/jna/pull/610): Fixed issue #604: Kernel32#GetLastError() always returns ERROR_SUCCESS [@lgoldstein](https://github.com/lgoldstein).
* [#634](https://github.com/java-native-access/jna/pull/634): Improve BSTR handling - [@matthiasblaesing](https://github.com/matthiasblaesing).

Release 4.2.1
=============
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
package com.sun.jna.platform.win32.COM;

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WTypes.BSTRByReference;
import com.sun.jna.platform.win32.WinNT.HRESULT;


/**
Expand Down Expand Up @@ -81,7 +79,7 @@ public interface IMoniker extends IPersistStream {
*
* @see <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms680754%28v=vs.85%29.aspx">MSDN</a>
*/
HRESULT GetDisplayName(Pointer pbc, Pointer pmkToLeft, BSTRByReference ppszDisplayName);
String GetDisplayName(Pointer bindContext, Pointer pmkToLeft);

void ParseDisplayName();

Expand Down
24 changes: 19 additions & 5 deletions contrib/platform/src/com/sun/jna/platform/win32/COM/Moniker.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@

import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WTypes.BSTRByReference;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.Guid.CLSID;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.platform.win32.Ole32;
import com.sun.jna.platform.win32.WTypes;
import com.sun.jna.ptr.PointerByReference;

public class Moniker extends Unknown implements IMoniker {

Expand Down Expand Up @@ -119,13 +120,26 @@ public void RelativePathTo() {
}

@Override
public HRESULT GetDisplayName(Pointer pbc, Pointer pmkToLeft, BSTRByReference ppszDisplayName) {
public String GetDisplayName(Pointer pbc, Pointer pmkToLeft) {
final int vTableId = vTableIdStart + 13;

PointerByReference ppszDisplayNameRef = new PointerByReference();

WinNT.HRESULT hr = (WinNT.HRESULT) this._invokeNativeObject(vTableId, new Object[] { this.getPointer(), pbc,
pmkToLeft, ppszDisplayName }, WinNT.HRESULT.class);
pmkToLeft, ppszDisplayNameRef }, WinNT.HRESULT.class);

return hr;
COMUtils.checkRC(hr);

Pointer ppszDisplayName = ppszDisplayNameRef.getValue();
if(ppszDisplayName == null) {
return null;
}

WTypes.LPOLESTR oleStr = new WTypes.LPOLESTR(ppszDisplayName);
String name = oleStr.getValue();
Ole32.INSTANCE.CoTaskMemFree(ppszDisplayName);

return name;
}

@Override
Expand Down
16 changes: 16 additions & 0 deletions contrib/platform/src/com/sun/jna/platform/win32/OleAuto.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,22 @@ public interface OleAuto extends StdCallLibrary {
*/
void SysFreeString(BSTR bstr);

/**
* Returns the length (in bytes) of a BSTR.
*
* @param bstr
* Unicode string that was allocated previously.
*/
int SysStringByteLen(BSTR bstr);

/**
* Returns the length of a BSTR.
*
* @param bstr
* Unicode string that was allocated previously.
*/
int SysStringLen(BSTR bstr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are new, might be nice to call them out in CHANGELOG.


/**
* The VariantInit function initializes the VARIANTARG by setting the vt
* field to VT_EMPTY. Unlike VariantClear, this function does not interpret
Expand Down
66 changes: 57 additions & 9 deletions contrib/platform/src/com/sun/jna/platform/win32/WTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WinDef.USHORT;
import com.sun.jna.ptr.ByReference;
import java.io.UnsupportedEncodingException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Constant defined in WTypes.h
Expand Down Expand Up @@ -61,35 +64,80 @@ public interface WTypes {
public static int CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER
| CLSCTX_LOCAL_SERVER;

/**
* BSTR wrapper.
*
* <p>From MSDN:</p>
*
* <blockquote>A BSTR (Basic string or binary string) is a string data type
* that is used by COM, Automation, and Interop functions. Use the BSTR data
* type in all interfaces that will be accessed from script.</blockquote>
*
* <p>The memory structure:</p>
*
* <dl>
* <dt>Length prefix</dt>
* <dd>Length of the data array holding the string data and does not include
* the final two NULL characters.</dd>
* <dt>Data string</dt>
* <dd>UTF-16LE encoded bytes for the string.</dd>
* <dt>Terminator</dt>
* <dd>Two null characters</dd>
* </dl>
*
* <p>The "value" of the BSTR is the pointer to the start of the Data String,
* the length prefix is the four bytes before that.</p>
*
* <p>The MSDN states, that a BSTR derived from a Nullpointer is treated
* as a string containing zero characters.</p>
*/
public static class BSTR extends PointerType {
public static class ByReference extends BSTR implements
Structure.ByReference {
}

public BSTR() {
super(new Memory(Pointer.SIZE));
super(Pointer.NULL);
}

public BSTR(Pointer pointer) {
super(pointer);
}

public BSTR(String value) {
super(new Memory((value.length() + 1L) * Native.WCHAR_SIZE));
super();
this.setValue(value);
}

public void setValue(String value) {
this.getPointer().setWideString(0, value);
if(value == null) {
value = "";
}
try {
byte[] encodedValue = value.getBytes("UTF-16LE");
// 4 bytes for the length prefix, length for the encoded data,
// 2 bytes for the two NULL terminators
Memory mem = new Memory(4 + encodedValue.length + 2);
mem.clear();
mem.setInt(0, encodedValue.length);
mem.write(4, encodedValue, 0, encodedValue.length);
this.setPointer(mem.share(4));
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("UTF-16LE charset is not supported", ex);
}
}

public String getValue() {
Pointer pointer = this.getPointer();
String str = null;
if (pointer != null)
str = pointer.getWideString(0);

return str;
try {
Pointer pointer = this.getPointer();
if(pointer == null) {
return "";
}
int stringLength = pointer.getInt(-4);
return new String(pointer.getByteArray(0, stringLength), "UTF-16LE");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("UTF-16LE charset is not supported", ex);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.sun.jna.platform.win32.Ole32;
import com.sun.jna.platform.win32.Guid.REFIID;
import com.sun.jna.platform.win32.WTypes;
import com.sun.jna.platform.win32.WTypes.BSTRByReference;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.ULONG;
Expand Down Expand Up @@ -114,13 +115,8 @@ public void GetObject() {

PointerByReference ppbc = new PointerByReference();
Ole32.INSTANCE.CreateBindCtx(new DWORD(), ppbc);
//IBindCtx pbc = new BindCtx(ppbc.getValue());

BSTRByReference ppszDisplayName = new BSTRByReference();
hr = moniker.GetDisplayName(ppbc.getValue(), moniker.getPointer(), ppszDisplayName);
COMUtils.checkRC(hr);
String name = ppszDisplayName.getString();
Ole32.INSTANCE.CoTaskMemFree(ppszDisplayName.getPointer().getPointer(0));

String name = moniker.GetDisplayName(ppbc.getValue(), moniker.getPointer());

PointerByReference ppunkObject = new PointerByReference();
hr = rot.GetObject(moniker.getPointer(), ppunkObject);
Expand Down
39 changes: 39 additions & 0 deletions contrib/platform/test/com/sun/jna/platform/win32/WTypesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WTypes.BSTR;
import junit.framework.TestCase;

public class WTypesTest extends TestCase {
Expand Down Expand Up @@ -53,7 +54,45 @@ public void testLPWSTRConstruction() {
WTypes.LPWSTR fromPointer = new WTypes.LPWSTR(TEST_POINTER);
assertEquals(fromPointer.getValue(), TEST_STRING);
}

public void testBSTRBasic() {
String demoString = "input\u00D6\u00E4\u00DC?!";
// Allocation via system and the "correct" way
BSTR sysAllocated = OleAuto.INSTANCE.SysAllocString(demoString);
// Java based allocation - not suitable if passed via automation
BSTR javaAllocated = new BSTR(demoString);

// Ensure encoding roundtripping works
assertEquals(demoString, sysAllocated.getValue());
assertEquals(demoString, javaAllocated.getValue());

// BSTR is encoded as UTF-16/UCS2, so byte length is 2 * char count
assertEquals(demoString.length(), OleAuto.INSTANCE.SysStringLen(sysAllocated));
assertEquals(demoString.length(), OleAuto.INSTANCE.SysStringLen(javaAllocated));
assertEquals(2 * demoString.length(), OleAuto.INSTANCE.SysStringByteLen(sysAllocated));
assertEquals(2 * demoString.length(), OleAuto.INSTANCE.SysStringByteLen(javaAllocated));

// The BSTR Pointer points 4 bytes into the data itself (beginning of data
// string, the 4 preceding bytes code the string length (in bytes)
assertEquals(2 * demoString.length(), sysAllocated.getPointer().getInt(-4));
assertEquals(2 * demoString.length(), javaAllocated.getPointer().getInt(-4));

OleAuto.INSTANCE.SysFreeString(sysAllocated);
// javaAllocated is allocated via Memory and will be freeed by the
// garbadge collector automaticly
}

public void testBSTRNullPointerHandling() {
// Allocation from NULL should return NULL
BSTR sysAllocated = OleAuto.INSTANCE.SysAllocString(null);
assertNull(sysAllocated);

// MSDN states, that the BSTR from Nullpointer represents the string with
// zero characters
BSTR bstr = new BSTR(Pointer.NULL);
assertEquals("", bstr.getValue());
}

public static void main(String[] args) {
junit.textui.TestRunner.run(WTypesTest.class);
}
Expand Down