Skip to content

Commit 87a7c98

Browse files
committed
Merge pull request #634 from matthiasblaesing/bstr_cleanup
Improve BSTR Handling
2 parents faba63d + 45202a1 commit 87a7c98

File tree

7 files changed

+136
-24
lines changed

7 files changed

+136
-24
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Bug Fixes
6161
* [#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).
6262
* [#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).
6363
* [#610](https://github.com/java-native-access/jna/pull/610): Fixed issue #604: Kernel32#GetLastError() always returns ERROR_SUCCESS [@lgoldstein](https://github.com/lgoldstein).
64+
* [#634](https://github.com/java-native-access/jna/pull/634): Improve BSTR handling - [@matthiasblaesing](https://github.com/matthiasblaesing).
6465

6566
Release 4.2.1
6667
=============

contrib/platform/src/com/sun/jna/platform/win32/COM/IMoniker.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
package com.sun.jna.platform.win32.COM;
1414

1515
import com.sun.jna.Pointer;
16-
import com.sun.jna.platform.win32.WTypes.BSTRByReference;
17-
import com.sun.jna.platform.win32.WinNT.HRESULT;
1816

1917

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

8684
void ParseDisplayName();
8785

contrib/platform/src/com/sun/jna/platform/win32/COM/Moniker.java

+19-5
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414

1515
import com.sun.jna.Pointer;
1616
import com.sun.jna.Structure;
17-
import com.sun.jna.platform.win32.WTypes.BSTRByReference;
1817
import com.sun.jna.platform.win32.WinNT;
1918
import com.sun.jna.platform.win32.Guid.CLSID;
20-
import com.sun.jna.platform.win32.WinNT.HRESULT;
19+
import com.sun.jna.platform.win32.Ole32;
20+
import com.sun.jna.platform.win32.WTypes;
21+
import com.sun.jna.ptr.PointerByReference;
2122

2223
public class Moniker extends Unknown implements IMoniker {
2324

@@ -119,13 +120,26 @@ public void RelativePathTo() {
119120
}
120121

121122
@Override
122-
public HRESULT GetDisplayName(Pointer pbc, Pointer pmkToLeft, BSTRByReference ppszDisplayName) {
123+
public String GetDisplayName(Pointer pbc, Pointer pmkToLeft) {
123124
final int vTableId = vTableIdStart + 13;
124125

126+
PointerByReference ppszDisplayNameRef = new PointerByReference();
127+
125128
WinNT.HRESULT hr = (WinNT.HRESULT) this._invokeNativeObject(vTableId, new Object[] { this.getPointer(), pbc,
126-
pmkToLeft, ppszDisplayName }, WinNT.HRESULT.class);
129+
pmkToLeft, ppszDisplayNameRef }, WinNT.HRESULT.class);
127130

128-
return hr;
131+
COMUtils.checkRC(hr);
132+
133+
Pointer ppszDisplayName = ppszDisplayNameRef.getValue();
134+
if(ppszDisplayName == null) {
135+
return null;
136+
}
137+
138+
WTypes.LPOLESTR oleStr = new WTypes.LPOLESTR(ppszDisplayName);
139+
String name = oleStr.getValue();
140+
Ole32.INSTANCE.CoTaskMemFree(ppszDisplayName);
141+
142+
return name;
129143
}
130144

131145
@Override

contrib/platform/src/com/sun/jna/platform/win32/OleAuto.java

+16
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,22 @@ public interface OleAuto extends StdCallLibrary {
121121
*/
122122
void SysFreeString(BSTR bstr);
123123

124+
/**
125+
* Returns the length (in bytes) of a BSTR.
126+
*
127+
* @param bstr
128+
* Unicode string that was allocated previously.
129+
*/
130+
int SysStringByteLen(BSTR bstr);
131+
132+
/**
133+
* Returns the length of a BSTR.
134+
*
135+
* @param bstr
136+
* Unicode string that was allocated previously.
137+
*/
138+
int SysStringLen(BSTR bstr);
139+
124140
/**
125141
* The VariantInit function initializes the VARIANTARG by setting the vt
126142
* field to VT_EMPTY. Unlike VariantClear, this function does not interpret

contrib/platform/src/com/sun/jna/platform/win32/WTypes.java

+57-9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import com.sun.jna.Structure;
2323
import com.sun.jna.platform.win32.WinDef.USHORT;
2424
import com.sun.jna.ptr.ByReference;
25+
import java.io.UnsupportedEncodingException;
26+
import java.util.logging.Level;
27+
import java.util.logging.Logger;
2528

2629
/**
2730
* Constant defined in WTypes.h
@@ -61,35 +64,80 @@ public interface WTypes {
6164
public static int CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER
6265
| CLSCTX_LOCAL_SERVER;
6366

67+
/**
68+
* BSTR wrapper.
69+
*
70+
* <p>From MSDN:</p>
71+
*
72+
* <blockquote>A BSTR (Basic string or binary string) is a string data type
73+
* that is used by COM, Automation, and Interop functions. Use the BSTR data
74+
* type in all interfaces that will be accessed from script.</blockquote>
75+
*
76+
* <p>The memory structure:</p>
77+
*
78+
* <dl>
79+
* <dt>Length prefix</dt>
80+
* <dd>Length of the data array holding the string data and does not include
81+
* the final two NULL characters.</dd>
82+
* <dt>Data string</dt>
83+
* <dd>UTF-16LE encoded bytes for the string.</dd>
84+
* <dt>Terminator</dt>
85+
* <dd>Two null characters</dd>
86+
* </dl>
87+
*
88+
* <p>The "value" of the BSTR is the pointer to the start of the Data String,
89+
* the length prefix is the four bytes before that.</p>
90+
*
91+
* <p>The MSDN states, that a BSTR derived from a Nullpointer is treated
92+
* as a string containing zero characters.</p>
93+
*/
6494
public static class BSTR extends PointerType {
6595
public static class ByReference extends BSTR implements
6696
Structure.ByReference {
6797
}
6898

6999
public BSTR() {
70-
super(new Memory(Pointer.SIZE));
100+
super(Pointer.NULL);
71101
}
72102

73103
public BSTR(Pointer pointer) {
74104
super(pointer);
75105
}
76106

77107
public BSTR(String value) {
78-
super(new Memory((value.length() + 1L) * Native.WCHAR_SIZE));
108+
super();
79109
this.setValue(value);
80110
}
81111

82112
public void setValue(String value) {
83-
this.getPointer().setWideString(0, value);
113+
if(value == null) {
114+
value = "";
115+
}
116+
try {
117+
byte[] encodedValue = value.getBytes("UTF-16LE");
118+
// 4 bytes for the length prefix, length for the encoded data,
119+
// 2 bytes for the two NULL terminators
120+
Memory mem = new Memory(4 + encodedValue.length + 2);
121+
mem.clear();
122+
mem.setInt(0, encodedValue.length);
123+
mem.write(4, encodedValue, 0, encodedValue.length);
124+
this.setPointer(mem.share(4));
125+
} catch (UnsupportedEncodingException ex) {
126+
throw new RuntimeException("UTF-16LE charset is not supported", ex);
127+
}
84128
}
85129

86130
public String getValue() {
87-
Pointer pointer = this.getPointer();
88-
String str = null;
89-
if (pointer != null)
90-
str = pointer.getWideString(0);
91-
92-
return str;
131+
try {
132+
Pointer pointer = this.getPointer();
133+
if(pointer == null) {
134+
return "";
135+
}
136+
int stringLength = pointer.getInt(-4);
137+
return new String(pointer.getByteArray(0, stringLength), "UTF-16LE");
138+
} catch (UnsupportedEncodingException ex) {
139+
throw new RuntimeException("UTF-16LE charset is not supported", ex);
140+
}
93141
}
94142

95143
@Override

contrib/platform/test/com/sun/jna/platform/win32/COM/RunningObjectTable_Test.java

+3-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.sun.jna.platform.win32.Ole32;
2323
import com.sun.jna.platform.win32.Guid.REFIID;
24+
import com.sun.jna.platform.win32.WTypes;
2425
import com.sun.jna.platform.win32.WTypes.BSTRByReference;
2526
import com.sun.jna.platform.win32.WinDef.DWORD;
2627
import com.sun.jna.platform.win32.WinDef.ULONG;
@@ -114,13 +115,8 @@ public void GetObject() {
114115

115116
PointerByReference ppbc = new PointerByReference();
116117
Ole32.INSTANCE.CreateBindCtx(new DWORD(), ppbc);
117-
//IBindCtx pbc = new BindCtx(ppbc.getValue());
118-
119-
BSTRByReference ppszDisplayName = new BSTRByReference();
120-
hr = moniker.GetDisplayName(ppbc.getValue(), moniker.getPointer(), ppszDisplayName);
121-
COMUtils.checkRC(hr);
122-
String name = ppszDisplayName.getString();
123-
Ole32.INSTANCE.CoTaskMemFree(ppszDisplayName.getPointer().getPointer(0));
118+
119+
String name = moniker.GetDisplayName(ppbc.getValue(), moniker.getPointer());
124120

125121
PointerByReference ppunkObject = new PointerByReference();
126122
hr = rot.GetObject(moniker.getPointer(), ppunkObject);

contrib/platform/test/com/sun/jna/platform/win32/WTypesTest.java

+39
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.sun.jna.Memory;
1616
import com.sun.jna.Native;
1717
import com.sun.jna.Pointer;
18+
import com.sun.jna.platform.win32.WTypes.BSTR;
1819
import junit.framework.TestCase;
1920

2021
public class WTypesTest extends TestCase {
@@ -53,7 +54,45 @@ public void testLPWSTRConstruction() {
5354
WTypes.LPWSTR fromPointer = new WTypes.LPWSTR(TEST_POINTER);
5455
assertEquals(fromPointer.getValue(), TEST_STRING);
5556
}
57+
58+
public void testBSTRBasic() {
59+
String demoString = "input\u00D6\u00E4\u00DC?!";
60+
// Allocation via system and the "correct" way
61+
BSTR sysAllocated = OleAuto.INSTANCE.SysAllocString(demoString);
62+
// Java based allocation - not suitable if passed via automation
63+
BSTR javaAllocated = new BSTR(demoString);
64+
65+
// Ensure encoding roundtripping works
66+
assertEquals(demoString, sysAllocated.getValue());
67+
assertEquals(demoString, javaAllocated.getValue());
68+
69+
// BSTR is encoded as UTF-16/UCS2, so byte length is 2 * char count
70+
assertEquals(demoString.length(), OleAuto.INSTANCE.SysStringLen(sysAllocated));
71+
assertEquals(demoString.length(), OleAuto.INSTANCE.SysStringLen(javaAllocated));
72+
assertEquals(2 * demoString.length(), OleAuto.INSTANCE.SysStringByteLen(sysAllocated));
73+
assertEquals(2 * demoString.length(), OleAuto.INSTANCE.SysStringByteLen(javaAllocated));
74+
75+
// The BSTR Pointer points 4 bytes into the data itself (beginning of data
76+
// string, the 4 preceding bytes code the string length (in bytes)
77+
assertEquals(2 * demoString.length(), sysAllocated.getPointer().getInt(-4));
78+
assertEquals(2 * demoString.length(), javaAllocated.getPointer().getInt(-4));
79+
80+
OleAuto.INSTANCE.SysFreeString(sysAllocated);
81+
// javaAllocated is allocated via Memory and will be freeed by the
82+
// garbadge collector automaticly
83+
}
5684

85+
public void testBSTRNullPointerHandling() {
86+
// Allocation from NULL should return NULL
87+
BSTR sysAllocated = OleAuto.INSTANCE.SysAllocString(null);
88+
assertNull(sysAllocated);
89+
90+
// MSDN states, that the BSTR from Nullpointer represents the string with
91+
// zero characters
92+
BSTR bstr = new BSTR(Pointer.NULL);
93+
assertEquals("", bstr.getValue());
94+
}
95+
5796
public static void main(String[] args) {
5897
junit.textui.TestRunner.run(WTypesTest.class);
5998
}

0 commit comments

Comments
 (0)