From 45202a1f1e56fe8cd28856b53c5774345b5d8d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sun, 10 Apr 2016 20:32:13 +0200 Subject: [PATCH] Improve BSTR Handling --- CHANGES.md | 1 + .../sun/jna/platform/win32/COM/IMoniker.java | 4 +- .../sun/jna/platform/win32/COM/Moniker.java | 24 +++++-- .../com/sun/jna/platform/win32/OleAuto.java | 16 +++++ .../com/sun/jna/platform/win32/WTypes.java | 66 ++++++++++++++++--- .../win32/COM/RunningObjectTable_Test.java | 10 +-- .../sun/jna/platform/win32/WTypesTest.java | 39 +++++++++++ 7 files changed, 136 insertions(+), 24 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9db3ac28df..d369e18161 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 ============= diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/IMoniker.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/IMoniker.java index d6beb1e537..5908bc23b9 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/IMoniker.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/IMoniker.java @@ -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; /** @@ -81,7 +79,7 @@ public interface IMoniker extends IPersistStream { * * @see MSDN */ - HRESULT GetDisplayName(Pointer pbc, Pointer pmkToLeft, BSTRByReference ppszDisplayName); + String GetDisplayName(Pointer bindContext, Pointer pmkToLeft); void ParseDisplayName(); diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/Moniker.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/Moniker.java index b332719d71..4647dc57bf 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/Moniker.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/Moniker.java @@ -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 { @@ -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 diff --git a/contrib/platform/src/com/sun/jna/platform/win32/OleAuto.java b/contrib/platform/src/com/sun/jna/platform/win32/OleAuto.java index f6e71c41b4..a7af894e49 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/OleAuto.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/OleAuto.java @@ -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); + /** * The VariantInit function initializes the VARIANTARG by setting the vt * field to VT_EMPTY. Unlike VariantClear, this function does not interpret diff --git a/contrib/platform/src/com/sun/jna/platform/win32/WTypes.java b/contrib/platform/src/com/sun/jna/platform/win32/WTypes.java index 7f8762f739..35879e0649 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/WTypes.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/WTypes.java @@ -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 @@ -61,13 +64,40 @@ public interface WTypes { public static int CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER; + /** + * BSTR wrapper. + * + *

From MSDN:

+ * + *
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.
+ * + *

The memory structure:

+ * + *
+ *
Length prefix
+ *
Length of the data array holding the string data and does not include + * the final two NULL characters.
+ *
Data string
+ *
UTF-16LE encoded bytes for the string.
+ *
Terminator
+ *
Two null characters
+ *
+ * + *

The "value" of the BSTR is the pointer to the start of the Data String, + * the length prefix is the four bytes before that.

+ * + *

The MSDN states, that a BSTR derived from a Nullpointer is treated + * as a string containing zero characters.

+ */ 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) { @@ -75,21 +105,39 @@ public BSTR(Pointer 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 diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/RunningObjectTable_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/RunningObjectTable_Test.java index 1c895cd2b2..ffa14c0e4f 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/RunningObjectTable_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/RunningObjectTable_Test.java @@ -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; @@ -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); diff --git a/contrib/platform/test/com/sun/jna/platform/win32/WTypesTest.java b/contrib/platform/test/com/sun/jna/platform/win32/WTypesTest.java index cf01c210b2..91dd0ba93f 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/WTypesTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/WTypesTest.java @@ -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 { @@ -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); }