From 5ab9cd0655112d99a5918f7cea4a20b3fc26b4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sun, 14 Feb 2016 20:24:58 +0100 Subject: [PATCH] Allow calling COM methods/getters requirering hybrid calling (METHOD+PROPERTYGET convention) It was found, that calling InchesToPoints method from word type library failed. Research lead to https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/ https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx Summary: there are methods in the word typelibrary that require both PROPERTYGET _and_ METHOD to be set. With only one of these set the call fails. The article from delphitools argues, that automation compatible libraries need to be compatible with VisualBasic which does not distingish methods and property getters and will set both flags always. The MSDN article advises this behaviour: "[...] Some languages cannot distinguish between retrieving a property and calling a method. In this case, you should set the flags DISPATCH_PROPERTYGET and DISPATCH_METHOD. [...]" This further support the advised way from delphitools and was implemented. --- CHANGES.md | 1 + .../win32/COM/COMBindingBaseObject.java | 33 +++- .../platform/win32/COM/util/ProxyObject.java | 32 +++- .../jna/platform/win32/COM/IDispatchTest.java | 2 +- .../COM/util/HybdridCOMInvocationTest.java | 170 ++++++++++++++++++ .../COM/util/RunningObjectTable_Test.java | 4 +- 6 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 contrib/platform/test/com/sun/jna/platform/win32/COM/util/HybdridCOMInvocationTest.java diff --git a/CHANGES.md b/CHANGES.md index 5e28d10f60..3c980d4ce0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,7 @@ Features * [#569](https://github.com/java-native-access/jna/pull/569): Added `com.sun.jna.platform.win32.Winspool.PRINTER_INFO_2` support. Added GetPrinter and ClosePrinter functions in `com.sun.jna.platform.win32.Winspool` - [@IvanRF](https://github.com/IvanRF). * [#583](https://github.com/java-native-access/jna/pull/583): Added printer attributes and status - [@IvanRF](https://github.com/IvanRF). * [#589](https://github.com/java-native-access/jna/pull/589): Use MethodResultContext in direct mapping (as done in interface mapping) - [@marco2357](https://github.com/marco2357). +* [#595](https://github.com/java-native-access/jna/pull/595): Allow calling COM methods/getters requiring hybrid calling (METHOD+PROPERTYGET) - [@matthiasblaesing](https://github.com/matthiasblaesing). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java index c7de50325f..55b2fe424b 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java @@ -262,9 +262,40 @@ protected HRESULT oleMethod(int nType, VARIANT.ByReference pvResult, dp.write(); } + // Apply "fix" according to + // https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/ + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx + // + // Summary: there are methods in the word typelibrary that require both + // PROPERTYGET _and_ METHOD to be set. With only one of these set the call + // fails. + // + // The article from delphitools argues, that automation compatible libraries + // need to be compatible with VisualBasic which does not distingish methods + // and property getters and will set both flags always. + // + // The MSDN article advises this behaviour: "[...] Some languages cannot + // distinguish between retrieving a property and calling a method. In this + //case, you should set the flags DISPATCH_PROPERTYGET and DISPATCH_METHOD. + // [...]")) + // + // This was found when trying to bind InchesToPoints from the _Application + // dispatch interface of the MS Word 15 type library + // + // The signature according the ITypeLib Viewer (OLE/COM Object Viewer): + // [id(0x00000172), helpcontext(0x09700172)] + // single InchesToPoints([in] single Inches); + + final int finalNType; + if (nType == OleAuto.DISPATCH_METHOD || nType == OleAuto.DISPATCH_PROPERTYGET) { + finalNType = OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET; + } else { + finalNType = nType; + } + // Make the call! HRESULT hr = pDisp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, - new WinDef.WORD(nType), dp, pvResult, pExcepInfo, puArgErr); + new WinDef.WORD(finalNType), dp, pvResult, pExcepInfo, puArgErr); COMUtils.checkRC(hr, pExcepInfo, puArgErr); return hr; diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ProxyObject.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ProxyObject.java index ffd0a59e21..40a1e64b28 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ProxyObject.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ProxyObject.java @@ -611,6 +611,36 @@ protected HRESULT oleMethod(final int nType, final VARIANT.ByReference pvResult, dp.cNamedArgs = new UINT(_argsLen); dp.rgdispidNamedArgs = new DISPIDByReference(OaIdl.DISPID_PROPERTYPUT); } + + // Apply "fix" according to + // https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/ + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx + // + // Summary: there are methods in the word typelibrary that require both + // PROPERTYGET _and_ METHOD to be set. With only one of these set the call + // fails. + // + // The article from delphitools argues, that automation compatible libraries + // need to be compatible with VisualBasic which does not distingish methods + // and property getters and will set both flags always. + // + // The MSDN article advises this behaviour: "[...] Some languages cannot + // distinguish between retrieving a property and calling a method. In this + //case, you should set the flags DISPATCH_PROPERTYGET and DISPATCH_METHOD. + // [...]")) + // + // This was found when trying to bind InchesToPoints from the _Application + // dispatch interface of the MS Word 15 type library + // + // The signature according the ITypeLib Viewer (OLE/COM Object Viewer): + // [id(0x00000172), helpcontext(0x09700172)] + // single InchesToPoints([in] single Inches); + final int finalNType; + if(nType == OleAuto.DISPATCH_METHOD || nType == OleAuto.DISPATCH_PROPERTYGET) { + finalNType = OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET; + } else { + finalNType = nType; + } // Build DISPPARAMS if (_argsLen > 0) { @@ -629,7 +659,7 @@ protected HRESULT oleMethod(final int nType, final VARIANT.ByReference pvResult, @Override public HRESULT call() throws Exception { return pDisp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, - new WinDef.WORD(nType), dp, pvResult, pExcepInfo, puArgErr); + new WinDef.WORD(finalNType), dp, pvResult, pExcepInfo, puArgErr); } }); diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/IDispatchTest.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/IDispatchTest.java index d13b5273d8..3e3a4af58c 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/IDispatchTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/IDispatchTest.java @@ -39,7 +39,7 @@ private Dispatch createIDispatch() { try { PointerByReference pDispatch = new PointerByReference(); - // Get CLSID for Word.Application... + // Get CLSID for Shell.Application... CLSID.ByReference clsid = new CLSID.ByReference(); HRESULT hr = Ole32.INSTANCE.CLSIDFromProgID("Shell.Application", clsid); diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/HybdridCOMInvocationTest.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/HybdridCOMInvocationTest.java new file mode 100644 index 0000000000..c78223ec2c --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/HybdridCOMInvocationTest.java @@ -0,0 +1,170 @@ +package com.sun.jna.platform.win32.COM.util; + +import com.sun.jna.platform.win32.COM.COMException; +import com.sun.jna.platform.win32.COM.COMLateBindingObject; +import com.sun.jna.platform.win32.COM.COMUtils; +import com.sun.jna.platform.win32.COM.Dispatch; +import com.sun.jna.platform.win32.COM.util.annotation.ComInterface; +import com.sun.jna.platform.win32.COM.util.annotation.ComMethod; +import com.sun.jna.platform.win32.COM.util.annotation.ComObject; +import com.sun.jna.platform.win32.Guid; +import com.sun.jna.platform.win32.Guid.CLSID; +import com.sun.jna.platform.win32.Guid.GUID; +import com.sun.jna.platform.win32.Guid.IID; +import com.sun.jna.platform.win32.Guid.REFIID; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.OaIdl; +import com.sun.jna.platform.win32.OaIdl.DISPID; +import com.sun.jna.platform.win32.Ole32; +import com.sun.jna.platform.win32.OleAuto; +import com.sun.jna.platform.win32.Variant; +import com.sun.jna.platform.win32.Variant.VARIANT; +import com.sun.jna.platform.win32.WTypes; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.platform.win32.WinDef.UINT; +import com.sun.jna.platform.win32.WinDef.WORD; +import com.sun.jna.platform.win32.WinNT.HRESULT; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.ptr.PointerByReference; +import java.util.logging.Level; +import java.util.logging.Logger; +import junit.framework.TestCase; +import org.junit.Test; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +/** + * In the word COM bindings it was determined, that some methods can't be called + * with only wFlags OleAuto.DISPATCH_METHOD or OleAuto.DISPATCH_PROPERTYGET. + * + * For these methods both flags need to be set. + * + * https://www.delphitools.info/2013/04/30/gaining-visual-basic-ole-super-powers/ + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms221486(v=vs.85).aspx + * + * A sample function is InchesToPoints from thw word typelibrary + */ +public class HybdridCOMInvocationTest extends TestCase { + + private static final Logger LOG = Logger.getLogger(HybdridCOMInvocationTest.class.getName()); + + private static final String CLSID_WORD_STRING = "{000209FF-0000-0000-C000-000000000046}"; + private static final String IID_APPLICATION_STRING = "{00020970-0000-0000-C000-000000000046}"; + private static final GUID CLSID_WORD = new GUID(CLSID_WORD_STRING); + private static final IID IID_APPLICATION = new IID(new GUID(IID_APPLICATION_STRING)); + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + Ole32.INSTANCE.CoUninitialize(); + } + + @Override + protected void setUp() throws Exception { + // Initialize COM for this thread... + HRESULT hr = Ole32.INSTANCE.CoInitialize(null); + } + + @Test + public void testOfficeInvocationProblemCOMUtil() { + Factory fact = new Factory(); + Application app; + try { + app = fact.createObject(Application.class); + } catch (COMException ex) { + LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.", ex); + return; + } + // If this fails: remember: floats are not exact, if this happens replace + // with a range check + assertEquals(72.0f, app.InchesToPoints(1F)); + } + + @Test + public void testOfficeInvocationProblemCOMBindingObject() { + WordApplication app; + try { + app = new WordApplication(false); + } catch (COMException ex) { + LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated.", ex); + return; + } + assertEquals(72.0f, app.InchesToPoints(1F)); + } + + + public void testOfficeInvocationDemonstration() { + // THIS IS NOT A TEST + // + // This reproduces the problem by using the dispatch directly. + + PointerByReference pDispatch = new PointerByReference(); + + HRESULT hr = Ole32.INSTANCE.CoCreateInstance(CLSID_WORD, null, + WTypes.CLSCTX_SERVER, IID_APPLICATION, pDispatch); + + if(! COMUtils.SUCCEEDED(hr)) { + LOG.log(Level.INFO, "HybdridCOMInvocationTest test was not run, MS Word object could not be instantiated."); + return; + } + + Dispatch dp = new Dispatch(pDispatch.getValue()); + + // DispID of InchesToPoints + DISPID dispId = new OaIdl.DISPID(0x00000172); + // Interface _Application of MS Word type library + WinDef.LCID LOCALE_SYSTEM_DEFAULT = Kernel32.INSTANCE.GetSystemDefaultLCID(); + Variant.VARIANT.ByReference result = new Variant.VARIANT.ByReference(); + OaIdl.EXCEPINFO.ByReference pExcepInfo = new OaIdl.EXCEPINFO.ByReference(); + IntByReference puArgErr = new IntByReference(); + + WORD wFlagsMethod = new WinDef.WORD(OleAuto.DISPATCH_METHOD); + WORD wFlagsGet = new WinDef.WORD(OleAuto.DISPATCH_PROPERTYGET); + WORD wFlagsCombined = new WinDef.WORD(OleAuto.DISPATCH_METHOD | OleAuto.DISPATCH_PROPERTYGET); + + OleAuto.DISPPARAMS.ByReference pDispParams = new OleAuto.DISPPARAMS.ByReference(); + VARIANT[] params = new VARIANT[1]; + params[0] = new VARIANT(1f); + pDispParams.cArgs = new UINT(1); + pDispParams.cNamedArgs = new UINT(0); + pDispParams.rgvarg = new Variant.VariantArg.ByReference(params); + pDispParams.rgdispidNamedArgs = new OaIdl.DISPIDByReference(); + + // Call InchesToPoints as a method + hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsMethod, pDispParams, result, pExcepInfo, puArgErr); + assertTrue(COMUtils.FAILED(hr)); + + // Call InchesToPoints as a property getter + hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsGet, pDispParams, result, pExcepInfo, puArgErr); + assertTrue(COMUtils.FAILED(hr)); + + // Call InchesToPoints as a hybrid + hr = dp.Invoke(dispId, new REFIID(Guid.IID_NULL), LOCALE_SYSTEM_DEFAULT, wFlagsCombined, pDispParams, result, pExcepInfo, puArgErr); + assertTrue(COMUtils.SUCCEEDED(hr)); + + assertEquals(72.0f, result.floatValue()); + } + + @ComObject(clsId = CLSID_WORD_STRING) + public static interface Application extends IDispatch, _Application { + } + + @ComInterface(iid = IID_APPLICATION_STRING) + public static interface _Application { + @ComMethod + Float InchesToPoints(Float value); + } + + public static class WordApplication extends COMLateBindingObject { + + public WordApplication(boolean useActiveInstance) { + super(new CLSID(CLSID_WORD), useActiveInstance); + } + + public Float InchesToPoints(Float value) { + VARIANT.ByReference pvResult = new VARIANT.ByReference(); + this.oleMethod(OleAuto.DISPATCH_METHOD , pvResult, this.getIDispatch(), "InchesToPoints", new VARIANT[] {new VARIANT(value)}); + return pvResult.floatValue(); + } + } +} diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/RunningObjectTable_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/RunningObjectTable_Test.java index 6d7c85f11b..968a219ba7 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/RunningObjectTable_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/RunningObjectTable_Test.java @@ -31,10 +31,10 @@ public class RunningObjectTable_Test { @ComInterface(iid="{00020970-0000-0000-C000-000000000046}") interface Application extends IUnknown { @ComProperty - boolean getVisible(); + Boolean getVisible(); @ComProperty - void setVisible(boolean value); + void setVisible(Boolean value); @ComMethod void Quit(boolean SaveChanges, Object OriginalFormat, Boolean RouteDocument);