From 95d845caec567c358ab594bd664a366390bd1afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sun, 10 Jan 2016 22:06:57 +0100 Subject: [PATCH 1/5] Make REFIID a real pointer type to fix native callbacks After the change introduced in d7f91f118df04b6966ae9539c9e7607c48be6df8 native callbacks for the COM handling error out (see below). Analysing the stack trace this happens after the java function was called. There structures are syncing back from java to native. I asume the calling programm passes in a readonly version of the IID and so the write fails. This code path was not hit before the above mentioned changeset, because there is a typeguard, that prevents syncing for ByValue calls (which were removed be the changeset). In addition to this, a fix ComEventCallbacks_Test to not depend on an installed office was commited. Now the Internet Explorer is used, as it can be expected to be present. Tests from ConnectionPointerContainer_Test duplicated a good part of ComEventCallbacks_Test so the cases were integrated into ComEventCallbacks_Test. The exception leading to this fix (CallbackReference.java:513 synchronised structed passed by reference after the java invocation): JNA: Callback com.sun.jna.platform.win32.COM.DispatchListener$1@233c0b17 threw the following exception: java.lang.Error: Invalid memory access at com.sun.jna.Native.setInt(Native Method) at com.sun.jna.Pointer.setInt(Pointer.java:1124) at com.sun.jna.Pointer.setValue(Pointer.java:925) at com.sun.jna.Structure.writeField(Structure.java:842) at com.sun.jna.Structure.write(Structure.java:754) at com.sun.jna.Structure.autoWrite(Structure.java:2047) at com.sun.jna.CallbackReference$DefaultCallbackProxy.invokeCallback(CallbackReference.java:513) at com.sun.jna.CallbackReference$DefaultCallbackProxy.callback(CallbackReference.java:528) at com.sun.jna.Native.invokeInt(Native Method) at com.sun.jna.Function.invoke(Function.java:390) at com.sun.jna.Function.invoke(Function.java:323) at com.sun.jna.Function.invoke(Function.java:275) at com.sun.jna.Function.invoke(Function.java:266) at com.sun.jna.platform.win32.COM.COMInvoker._invokeNativeObject(COMInvoker.java:37) at com.sun.jna.platform.win32.COM.ConnectionPoint.Advise(ConnectionPoint.java:42) at com.sun.jna.platform.win32.COM.ComEventCallbacks_Test.cause_Quit_Event(ComEventCallbacks_Test.java:236) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at junit.framework.JUnit4TestAdapter.run(JUnit4TestAdapter.java:38) at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.run(JUnitTestRunner.java:532) at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.launch(JUnitTestRunner.java:1179) at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.main(JUnitTestRunner.java:1030) --- .../src/com/sun/jna/platform/win32/Guid.java | 46 +- .../win32/COM/ComEventCallbacks_Test.java | 565 +++++++++++------- .../COM/ConnectionPointContainer_Test.java | 239 -------- 3 files changed, 392 insertions(+), 458 deletions(-) delete mode 100644 contrib/platform/test/com/sun/jna/platform/win32/COM/ConnectionPointContainer_Test.java diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Guid.java b/contrib/platform/src/com/sun/jna/platform/win32/Guid.java index 1ac91bb87c..f6794aec85 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Guid.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Guid.java @@ -17,9 +17,9 @@ import java.util.List; import com.sun.jna.Pointer; +import com.sun.jna.PointerType; import com.sun.jna.Structure; -// TODO: Auto-generated Javadoc /** * Ported from Guid.h. Microsoft Windows SDK 6.0A. * @@ -465,17 +465,30 @@ public CLSID(GUID guid) { } /** - * The Class REFIID. - * - * @author Tobias Wolf, wolf.tobias@gmx.net + * REFIID is a pointer to an IID. + * + * This type needs to be seperate from IID, as the REFIID can be passed in + * from external code, that does not allow writes to memory. + * + * With the normal JNA behaviour a structure, that crosses the native<->Java + * border will be autowritten, which causes a fault when written. + * Observed was this behaviour in COM-Callbacks, which get the REFIID passed + * into Invoke-method. + * + * So a IID can't be used directly, although the typedef of REFIID (from MSDN): + * + * typedef IID* REFIID; + * + * and the jna behaviour is described as: + * + * "When a function requires a pointer to a struct, a Java Structure should be used." */ - public class REFIID extends IID { + public class REFIID extends PointerType { /** * Instantiates a new refiid. */ public REFIID() { - super(); } /** @@ -488,21 +501,20 @@ public REFIID(Pointer memory) { super(memory); } - /** - * Instantiates a new refiid. - * - * @param data - * the data - */ - public REFIID(byte[] data) { - super(data); + public REFIID(IID guid) { + super(guid.getPointer()); + } + + public void setValue(IID value) { + setPointer(value.getPointer()); } - public REFIID(GUID guid) { - super(guid); + public IID getValue() { + return new IID(getPointer()); } + } - + /** * The Class IID. * diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/ComEventCallbacks_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/ComEventCallbacks_Test.java index f7b8a0ce67..4b730f4c4c 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/ComEventCallbacks_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/ComEventCallbacks_Test.java @@ -1,202 +1,363 @@ -/* Copyright (c) 2014 Dr David H. Akehurst (itemis), All Rights Reserved - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - */ -package com.sun.jna.platform.win32.COM; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.sun.jna.Pointer; -import com.sun.jna.WString; -import com.sun.jna.platform.win32.Guid; -import com.sun.jna.platform.win32.Guid.CLSID; -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.DISPID; -import com.sun.jna.platform.win32.OaIdl.DISPIDByReference; -import com.sun.jna.platform.win32.OaIdl.EXCEPINFO; -import com.sun.jna.platform.win32.Ole32; -import com.sun.jna.platform.win32.OleAuto.DISPPARAMS; -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.DWORDByReference; -import com.sun.jna.platform.win32.WinDef.LCID; -import com.sun.jna.platform.win32.WinDef.UINT; -import com.sun.jna.platform.win32.WinDef.UINTByReference; -import com.sun.jna.platform.win32.WinDef.WORD; -import com.sun.jna.platform.win32.WinError; -import com.sun.jna.platform.win32.WinNT.HRESULT; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.PointerByReference; - -public class ComEventCallbacks_Test { - - final String WORD_APPLICATION_INTERFACE = "{00020970-0000-0000-C000-000000000046}"; - final String APPLICATION_EVENTS_4 = "{00020A01-0000-0000-C000-000000000046}"; - - @Before - public void before() { - HRESULT hr = Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_MULTITHREADED); - COMUtils.checkRC(hr); - } - - @After - public void after() { - Ole32.INSTANCE.CoUninitialize(); - } - - class Application_Events4 implements IDispatchCallback { - public DispatchListener listener = new DispatchListener(this); - - @Override - public Pointer getPointer() { - return this.listener.getPointer(); - } - - //------------------------ IDispatch ------------------------------ - @Override - public HRESULT GetTypeInfoCount(UINTByReference pctinfo) { - return new HRESULT(WinError.E_NOTIMPL); - } - - @Override - public HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, PointerByReference ppTInfo) { - return new HRESULT(WinError.E_NOTIMPL); - } - - @Override - public HRESULT GetIDsOfNames(REFIID riid, WString[] rgszNames, int cNames, LCID lcid, DISPIDByReference rgDispId) { - return new HRESULT(WinError.E_NOTIMPL); - } - - public boolean Invoke_called = false; - @Override - public HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, - WORD wFlags, DISPPARAMS.ByReference pDispParams, - VARIANT.ByReference pVarResult, EXCEPINFO.ByReference pExcepInfo, - IntByReference puArgErr) { - this.Invoke_called = true; - - return new HRESULT(WinError.E_NOTIMPL); - } - - - //------------------------ IUnknown ------------------------------ - public boolean QueryInterface_called = false; - @Override - public HRESULT QueryInterface(REFIID refid, PointerByReference ppvObject) { - this.QueryInterface_called = true; - if (null==ppvObject) { - return new HRESULT(WinError.E_POINTER); - } - - String s = refid.toGuidString(); - IID appEvnts4 = new IID(APPLICATION_EVENTS_4); - REFIID riid = new REFIID(appEvnts4.getPointer()); - - if (refid.equals(riid)) { - ppvObject.setValue(this.getPointer()); - return WinError.S_OK; - } - - if (new Guid.IID(refid.getPointer()).equals(Unknown.IID_IUNKNOWN)) { - ppvObject.setValue(this.getPointer()); - return WinError.S_OK; - } - - if (new Guid.IID(refid.getPointer()).equals(Dispatch.IID_IDISPATCH)) { - ppvObject.setValue(this.getPointer()); - return WinError.S_OK; - } - - return new HRESULT(WinError.E_NOINTERFACE); - } - - public int AddRef() { - return 0; - } - - public int Release() { - return 0; - } - - } - - @Test - public void cause_Quit_Event() { - // Create word object - CLSID clsid = new CLSID("{000209FF-0000-0000-C000-000000000046}"); - PointerByReference ppWordApp = new PointerByReference(); - HRESULT hr = Ole32.INSTANCE - .CoCreateInstance(clsid, null, WTypes.CLSCTX_SERVER, IDispatch.IID_IDISPATCH, ppWordApp); -// HRESULT hr =OleAuto.INSTANCE.GetActiveObject(clsid, null, ppWordApp); - COMUtils.checkRC(hr); - - // query for ConnectionPointContainer - Unknown unk = new Unknown(ppWordApp.getValue()); - PointerByReference ppCpc = new PointerByReference(); - IID cpcIID = new IID("{B196B284-BAB4-101A-B69C-00AA00341D07}"); - hr = unk.QueryInterface(new REFIID(cpcIID), ppCpc); - COMUtils.checkRC(hr); - ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); - - // find connection point for Application_Events4 - IID appEvnts4 = new IID(APPLICATION_EVENTS_4); - REFIID riid = new REFIID(appEvnts4.getPointer()); - PointerByReference ppCp = new PointerByReference(); - hr = cpc.FindConnectionPoint(riid, ppCp); - COMUtils.checkRC(hr); - final ConnectionPoint cp = new ConnectionPoint(ppCp.getValue()); - IID cp_iid = new IID(); - hr = cp.GetConnectionInterface(cp_iid); - COMUtils.checkRC(hr); - - final Application_Events4 listener = new Application_Events4(); - final DWORDByReference pdwCookie = new DWORDByReference(); - HRESULT hr1 = cp.Advise(listener, pdwCookie); - COMUtils.checkRC(hr1); - -// Assert.assertTrue(listener.QueryInterface_called); -// - // Call Quit - Dispatch d = new Dispatch(ppWordApp.getValue()); - DISPID dispIdMember = new DISPID(1105); // Quit - REFIID niid = new REFIID(Guid.IID_NULL); - LCID lcid = Kernel32.INSTANCE.GetSystemDefaultLCID(); - WinDef.WORD wFlags = new WinDef.WORD(1); - DISPPARAMS.ByReference pDispParams = new DISPPARAMS.ByReference(); - VARIANT.ByReference pVarResult = new VARIANT.ByReference(); - IntByReference puArgErr = new IntByReference(); - EXCEPINFO.ByReference pExcepInfo = new EXCEPINFO.ByReference(); - hr = d.Invoke(dispIdMember, niid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); - COMUtils.checkRC(hr); - - //Wait for event to happen - try { - Thread.sleep(200); -// WinUser.MSG msg = new WinUser.MSG(); -// while (((User32.INSTANCE.GetMessage(msg, null, 0, 0)) != 0)) { -// User32.INSTANCE.TranslateMessage(msg); -// User32.INSTANCE.DispatchMessage(msg); -// } - } catch (Exception e) { - e.printStackTrace(); - } - - Assert.assertTrue(listener.Invoke_called); - } - -} +/* Copyright (c) 2014 Dr David H. Akehurst (itemis), All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ +package com.sun.jna.platform.win32.COM; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.sun.jna.Pointer; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.Guid; +import com.sun.jna.platform.win32.Guid.CLSID; +import com.sun.jna.platform.win32.Guid.IID; +import com.sun.jna.platform.win32.Guid.REFIID; +import com.sun.jna.platform.win32.OaIdl; +import com.sun.jna.platform.win32.OaIdl.DISPID; +import com.sun.jna.platform.win32.OaIdl.DISPIDByReference; +import com.sun.jna.platform.win32.OaIdl.EXCEPINFO; +import com.sun.jna.platform.win32.OaIdl.VARIANT_BOOLByReference; +import com.sun.jna.platform.win32.Ole32; +import com.sun.jna.platform.win32.OleAuto; +import com.sun.jna.platform.win32.OleAuto.DISPPARAMS; +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.DWORDByReference; +import com.sun.jna.platform.win32.WinDef.LCID; +import com.sun.jna.platform.win32.WinDef.UINT; +import com.sun.jna.platform.win32.WinDef.UINTByReference; +import com.sun.jna.platform.win32.WinDef.WORD; +import com.sun.jna.platform.win32.WinError; +import com.sun.jna.platform.win32.WinNT.HRESULT; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.ptr.PointerByReference; +import org.junit.Assert; + +public class ComEventCallbacks_Test { + + private final CLSID CLSID_InternetExplorer = new CLSID("{0002DF01-0000-0000-C000-000000000046}"); + private final IID IID_IConnectionPointContainer = new IID("{B196B284-BAB4-101A-B69C-00AA00341D07}"); + private final IID IID_DWebBrowserEvents2 = new IID("{34A715A0-6587-11D0-924A-0020AFC7AC4D}"); + private final REFIID niid = new REFIID(Guid.IID_NULL); + private final LCID lcid = new LCID(0x0409); // LCID for english locale + private final WinDef.WORD methodFlags = new WinDef.WORD(OleAuto.DISPATCH_METHOD); + private final WinDef.WORD propertyPutFlags = new WinDef.WORD(OleAuto.DISPATCH_PROPERTYPUT); + + private DISPIDByReference dispIdVisible = new DISPIDByReference(); + private DISPIDByReference dispIdQuit = new DISPIDByReference(); + private DISPIDByReference dispIdNavigate = new DISPIDByReference(); + + private PointerByReference ieApp; + private Dispatch ieDispatch; + + + + @Before + public void before() { + HRESULT hr = Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_MULTITHREADED); + COMUtils.checkRC(hr); + + // Create InternetExplorer object + ieApp = new PointerByReference(); + hr = Ole32.INSTANCE + .CoCreateInstance(CLSID_InternetExplorer, null, WTypes.CLSCTX_SERVER, IDispatch.IID_IDISPATCH, ieApp); + COMUtils.checkRC(hr); + + ieDispatch = new Dispatch(ieApp.getValue()); + ieDispatch.AddRef(); + hr = ieDispatch.GetIDsOfNames(new REFIID(Guid.IID_NULL), new WString[]{new WString("Quit")}, 1, lcid, dispIdQuit); + COMUtils.checkRC(hr); + hr = ieDispatch.GetIDsOfNames(new REFIID(Guid.IID_NULL), new WString[]{new WString("Visible")}, 1, lcid, dispIdVisible); + COMUtils.checkRC(hr); + hr = ieDispatch.GetIDsOfNames(new REFIID(Guid.IID_NULL), new WString[]{new WString("Navigate")}, 1, lcid, dispIdNavigate); + COMUtils.checkRC(hr); + } + + @After + public void after() { + // Shutdown Internet Explorer + DISPPARAMS.ByReference pDispParams = new DISPPARAMS.ByReference(); + pDispParams.cArgs = new UINT(0); + pDispParams.cNamedArgs = new UINT(0); + pDispParams.rgvarg = null; + VARIANT.ByReference pVarResult = new VARIANT.ByReference(); + IntByReference puArgErr = new IntByReference(); + EXCEPINFO.ByReference pExcepInfo = new EXCEPINFO.ByReference(); + + HRESULT hr = ieDispatch.Invoke(dispIdQuit.getValue(), niid, lcid, methodFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); + COMUtils.checkRC(hr, pExcepInfo, puArgErr); + + ieDispatch.Release(); + Ole32.INSTANCE.CoUninitialize(); + } + + @Test + public void queryInterface_ConnectionPointContainer() { + Unknown unk = new Unknown(ieApp.getValue()); + PointerByReference ppCpc = new PointerByReference(); + HRESULT hr = unk.QueryInterface(new REFIID(IID_IConnectionPointContainer), ppCpc); + COMUtils.checkRC(hr); + // On success the returned pointer must not be null + Assert.assertNotNull(ppCpc.getPointer()); + } + + @Test + public void FindConnectionPoint() { + // query for ConnectionPointContainer + Unknown unk = new Unknown(ieApp.getValue()); + PointerByReference ppCpc = new PointerByReference(); + HRESULT hr = unk.QueryInterface(new REFIID(IID_IConnectionPointContainer), ppCpc); + COMUtils.checkRC(hr); + ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); + + // find connection point for DWebBrowserEvents2 + REFIID riid = new REFIID(IID_DWebBrowserEvents2); + PointerByReference ppCp = new PointerByReference(); + hr = cpc.FindConnectionPoint(riid, ppCp); + COMUtils.checkRC(hr); + + // On success the returned pointer must not be null + Assert.assertNotNull(ppCpc.getPointer()); + } + + @Test + public void GetConnectionInterface() { + // query for ConnectionPointContainer + Unknown unk = new Unknown(this.ieApp.getValue()); + PointerByReference ppCpc = new PointerByReference(); + HRESULT hr = unk.QueryInterface(new REFIID(IID_IConnectionPointContainer), ppCpc); + COMUtils.checkRC(hr); + ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); + + // find connection point for DWebBrowserEvents2 + REFIID riid = new REFIID(IID_DWebBrowserEvents2); + PointerByReference ppCp = new PointerByReference(); + hr = cpc.FindConnectionPoint(riid, ppCp); + COMUtils.checkRC(hr); + ConnectionPoint cp = new ConnectionPoint(ppCp.getValue()); + + IID cp_iid = new IID(); + hr = cp.GetConnectionInterface(cp_iid); + COMUtils.checkRC(hr); + + Assert.assertEquals(IID_DWebBrowserEvents2, cp_iid); + } + + class DWebBrowserEvents2_Listener implements IDispatchCallback { + + private final int DISPID_NavigateComplete2 = 0x000000fc; + private final int DISPID_BeforeNavigate2 = 0x000000fa; + + public DispatchListener listener = new DispatchListener(this); + + @Override + public Pointer getPointer() { + return this.listener.getPointer(); + } + + //------------------------ IDispatch ------------------------------ + @Override + public HRESULT GetTypeInfoCount(UINTByReference pctinfo) { + return new HRESULT(WinError.E_NOTIMPL); + } + + @Override + public HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, PointerByReference ppTInfo) { + return new HRESULT(WinError.E_NOTIMPL); + } + + @Override + public HRESULT GetIDsOfNames(REFIID riid, WString[] rgszNames, int cNames, LCID lcid, DISPIDByReference rgDispId) { + return new HRESULT(WinError.E_NOTIMPL); + } + + public volatile boolean blockNavigation = false; + public volatile boolean navigateComplete2Called = false; + public volatile String navigateComplete2String = null; + + @Override + public HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS.ByReference pDispParams, + VARIANT.ByReference pVarResult, EXCEPINFO.ByReference pExcepInfo, + IntByReference puArgErr) { + + // @toDo: Move setArraySize into invoke method + if (pDispParams.rgvarg != null && pDispParams.cArgs.intValue() > 0) { + pDispParams.rgvarg.setArraySize(pDispParams.cArgs.intValue()); + } + + try { + switch (dispIdMember.intValue()) { + case DISPID_NavigateComplete2: + navigateComplete2Called = true; + // URL ist passed as VARIANT$ByReference + VARIANT urlByRef = pDispParams.rgvarg.variantArg[0]; + navigateComplete2String = ((VARIANT) urlByRef.getValue()).stringValue(); + break; + case DISPID_BeforeNavigate2: + VARIANT Cancel = pDispParams.rgvarg.variantArg[0]; + VARIANT Headers = pDispParams.rgvarg.variantArg[1]; + VARIANT PostData = pDispParams.rgvarg.variantArg[2]; + VARIANT TargetFrameName = pDispParams.rgvarg.variantArg[3]; + VARIANT Flags = pDispParams.rgvarg.variantArg[4]; + VARIANT URL = pDispParams.rgvarg.variantArg[5]; + VARIANT pDisp = pDispParams.rgvarg.variantArg[6]; + VARIANT_BOOLByReference cancelValue = ((VARIANT_BOOLByReference) Cancel.getValue()); + if (blockNavigation) { + cancelValue.setValue(Variant.VARIANT_TRUE); + } + break; + } + } catch (Throwable ex) { + ex.printStackTrace(System.out); + System.out.println(ex); + } + + return new HRESULT(WinError.E_NOTIMPL); + } + + //------------------------ IUnknown ------------------------------ + public volatile boolean QueryInterface_called = false; + + @Override + public HRESULT QueryInterface(REFIID refiid, PointerByReference ppvObject) { + this.QueryInterface_called = true; + if (null == ppvObject) { + return new HRESULT(WinError.E_POINTER); + } + + if (refiid.getValue().equals(IID_DWebBrowserEvents2)) { + ppvObject.setValue(this.getPointer()); + return WinError.S_OK; + } + + if (refiid.getValue().equals(Unknown.IID_IUNKNOWN)) { + ppvObject.setValue(this.getPointer()); + return WinError.S_OK; + } + + if (refiid.getValue().equals(Dispatch.IID_IDISPATCH)) { + ppvObject.setValue(this.getPointer()); + return WinError.S_OK; + } + + ppvObject.setValue(Pointer.NULL); + return new HRESULT(WinError.E_NOINTERFACE); + } + + public int AddRef() { + return 0; + } + + public int Release() { + return 0; + } + + } + + @Test + public void testComEventCallback() throws InterruptedException { + VARIANT.ByReference pVarResult = new VARIANT.ByReference(); + IntByReference puArgErr = new IntByReference(); + EXCEPINFO.ByReference pExcepInfo = new EXCEPINFO.ByReference(); + HRESULT hr; + + DISPPARAMS.ByReference pDispParams; + + pDispParams = new DISPPARAMS.ByReference(); + pDispParams.cArgs = new UINT(1); + pDispParams.cNamedArgs = new UINT(1); + pDispParams.rgvarg = new Variant.VariantArg.ByReference(new VARIANT[1]); + pDispParams.rgvarg.variantArg[0] = new VARIANT(true); + pDispParams.rgdispidNamedArgs = new DISPIDByReference(new DISPID(OaIdl.DISPID_PROPERTYPUT.intValue())); + // Visible-Prioperty + hr = ieDispatch.Invoke(dispIdVisible.getValue(), niid, lcid, propertyPutFlags, pDispParams, null, null, null); + COMUtils.checkRC(hr); + + // query for ConnectionPointContainer + Unknown unk = new Unknown(ieApp.getValue()); + PointerByReference ppCpc = new PointerByReference(); + hr = unk.QueryInterface(new REFIID(IID_IConnectionPointContainer), ppCpc); + COMUtils.checkRC(hr); + ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); + + // find connection point for DWebBrowserEvents2 + REFIID riid = new REFIID(IID_DWebBrowserEvents2); + PointerByReference ppCp = new PointerByReference(); + hr = cpc.FindConnectionPoint(riid, ppCp); + COMUtils.checkRC(hr); + final ConnectionPoint cp = new ConnectionPoint(ppCp.getValue()); + IID cp_iid = new IID(); + hr = cp.GetConnectionInterface(cp_iid); + COMUtils.checkRC(hr); + + final DWebBrowserEvents2_Listener listener = new DWebBrowserEvents2_Listener(); + final DWORDByReference pdwCookie = new DWORDByReference(); + HRESULT hr1 = cp.Advise(listener, pdwCookie); + COMUtils.checkRC(hr1); + + // Advise make several callbacks into the object passed in - at this + // point QueryInterface must have be called multiple times + Assert.assertTrue(listener.QueryInterface_called); + + // Call Navigate with URL https://github.com/java-native-access/jna + String navigateURL = "https://github.com/java-native-access/jna"; + String blockedURL = "http://www.google.de"; + + pDispParams = new DISPPARAMS.ByReference(); + pDispParams.cArgs = new UINT(1); + pDispParams.cNamedArgs = new UINT(0); + pDispParams.rgvarg = new Variant.VariantArg.ByReference(new VARIANT[1]); + pDispParams.rgvarg.variantArg[0] = new VARIANT(navigateURL); + hr = ieDispatch.Invoke(dispIdNavigate.getValue(), niid, lcid, methodFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); + COMUtils.checkRC(hr, pExcepInfo, puArgErr); + + for (int i = 0; i < 10; i++) { + if (listener.navigateComplete2Called) { + break; + } + Thread.sleep(1000); + } + + // At this point the call to Navigate before should be complete + Assert.assertTrue(listener.navigateComplete2Called); + // Navidate complete should have brought us to github + Assert.assertEquals(navigateURL, listener.navigateComplete2String); + + listener.navigateComplete2Called = false; + listener.navigateComplete2String = null; + listener.blockNavigation = true; + + pDispParams = new DISPPARAMS.ByReference(); + pDispParams.cArgs = new UINT(1); + pDispParams.cNamedArgs = new UINT(0); + pDispParams.rgvarg = new Variant.VariantArg.ByReference(new VARIANT[1]); + pDispParams.rgvarg.variantArg[0] = new VARIANT(blockedURL); + hr = ieDispatch.Invoke(dispIdNavigate.getValue(), niid, lcid, methodFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); + COMUtils.checkRC(hr, pExcepInfo, puArgErr); + + // wait 10 seconds to ensure navigation won't happen + for (int i = 0; i < 10; i++) { + if (listener.navigateComplete2Called) { + break; + } + Thread.sleep(1000); + } + + // Naviation will be blocked - so NavigateComplete can't be called + Assert.assertFalse("NavigateComplete Handler was called although it should be blocked", listener.navigateComplete2Called); + } + +} diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/ConnectionPointContainer_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/ConnectionPointContainer_Test.java deleted file mode 100644 index be1f41b1ba..0000000000 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/ConnectionPointContainer_Test.java +++ /dev/null @@ -1,239 +0,0 @@ -/* Copyright (c) 2014 Dr David H. Akehurst (itemis), All Rights Reserved - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - */ -package com.sun.jna.platform.win32.COM; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.sun.jna.Pointer; -import com.sun.jna.WString; -import com.sun.jna.platform.win32.Guid.CLSID; -import com.sun.jna.platform.win32.Guid.IID; -import com.sun.jna.platform.win32.Guid.REFIID; -import com.sun.jna.platform.win32.OaIdl.DISPID; -import com.sun.jna.platform.win32.OaIdl.DISPIDByReference; -import com.sun.jna.platform.win32.OaIdl.EXCEPINFO; -import com.sun.jna.platform.win32.OleAuto.DISPPARAMS; -import com.sun.jna.platform.win32.Guid; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.Ole32; -import com.sun.jna.platform.win32.WTypes; -import com.sun.jna.platform.win32.WinDef; -import com.sun.jna.platform.win32.WinError; -import com.sun.jna.platform.win32.Variant.VARIANT; -import com.sun.jna.platform.win32.WinDef.DWORDByReference; -import com.sun.jna.platform.win32.WinDef.LCID; -import com.sun.jna.platform.win32.WinDef.UINT; -import com.sun.jna.platform.win32.WinDef.UINTByReference; -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; - -public class ConnectionPointContainer_Test { - - PointerByReference ppWordApp; - - @Before - public void before() { - HRESULT hr = Ole32.INSTANCE.CoInitialize(null); - COMUtils.checkRC(hr); - - // Create word object - CLSID clsid = new CLSID("{000209FF-0000-0000-C000-000000000046}"); - this.ppWordApp = new PointerByReference(); - hr = Ole32.INSTANCE - .CoCreateInstance(clsid, null, WTypes.CLSCTX_SERVER, IDispatch.IID_IDISPATCH, this.ppWordApp); - COMUtils.checkRC(hr); - } - - @After - public void after() { - // Close Word - Dispatch d = new Dispatch(this.ppWordApp.getValue()); - DISPID dispIdMember = new DISPID(1105); // Quit - REFIID riid = new REFIID(Guid.IID_NULL); - LCID lcid = Kernel32.INSTANCE.GetSystemDefaultLCID(); - WinDef.WORD wFlags = new WinDef.WORD(1); - DISPPARAMS.ByReference pDispParams = new DISPPARAMS.ByReference(); - VARIANT.ByReference pVarResult = new VARIANT.ByReference(); - IntByReference puArgErr = new IntByReference(); - EXCEPINFO.ByReference pExcepInfo = new EXCEPINFO.ByReference(); - d.Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); - - Ole32.INSTANCE.CoUninitialize(); - } - - @Test - public void queryInterface_ConnectionPointContainer() { - Unknown unk = new Unknown(this.ppWordApp.getValue()); - PointerByReference ppCpc = new PointerByReference(); - IID cpcIID = new IID("{B196B284-BAB4-101A-B69C-00AA00341D07}"); - HRESULT hr = unk.QueryInterface(new REFIID(cpcIID), ppCpc); - COMUtils.checkRC(hr); - ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); - } - - @Test - public void FindConnectionPoint() { - // query for ConnectionPointContainer - Unknown unk = new Unknown(this.ppWordApp.getValue()); - PointerByReference ppCpc = new PointerByReference(); - IID cpcIID = new IID("{B196B284-BAB4-101A-B69C-00AA00341D07}"); - HRESULT hr = unk.QueryInterface(new REFIID(cpcIID), ppCpc); - COMUtils.checkRC(hr); - ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); - - // find connection point for Application_Events4 - IID appEvnts4 = new IID("{00020A01-0000-0000-C000-000000000046}"); - REFIID riid = new REFIID(appEvnts4.getPointer()); - PointerByReference ppCp = new PointerByReference(); - hr = cpc.FindConnectionPoint(riid, ppCp); - COMUtils.checkRC(hr); - ConnectionPoint cp = new ConnectionPoint(ppCp.getValue()); - } - - @Test - public void GetConnectionInterface() { - // query for ConnectionPointContainer - Unknown unk = new Unknown(this.ppWordApp.getValue()); - PointerByReference ppCpc = new PointerByReference(); - IID cpcIID = new IID("{B196B284-BAB4-101A-B69C-00AA00341D07}"); - HRESULT hr = unk.QueryInterface(new REFIID(cpcIID), ppCpc); - COMUtils.checkRC(hr); - ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); - - // find connection point for Application_Events4 - IID appEvnts4 = new IID("{00020A01-0000-0000-C000-000000000046}"); - REFIID riid = new REFIID(appEvnts4.getPointer()); - PointerByReference ppCp = new PointerByReference(); - hr = cpc.FindConnectionPoint(riid, ppCp); - COMUtils.checkRC(hr); - ConnectionPoint cp = new ConnectionPoint(ppCp.getValue()); - - IID cp_iid = new IID(); - hr = cp.GetConnectionInterface(cp_iid); - COMUtils.checkRC(hr); - - Assert.assertEquals(appEvnts4, cp_iid); - } - - class Application_Events4 implements IDispatchCallback { - public DispatchListener listener = new DispatchListener(this); - - @Override - public Pointer getPointer() { - return this.listener.getPointer(); - } - - //------------------------ IDispatch ------------------------------ - @Override - public HRESULT GetTypeInfoCount(UINTByReference pctinfo) { - return new HRESULT(WinError.E_NOTIMPL); - } - - @Override - public HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, PointerByReference ppTInfo) { - return new HRESULT(WinError.E_NOTIMPL); - } - - @Override - public HRESULT GetIDsOfNames(REFIID riid, WString[] rgszNames, int cNames, LCID lcid, DISPIDByReference rgDispId) { - return new HRESULT(WinError.E_NOTIMPL); - } - - public boolean Invoke_called = false; - @Override - public HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, - DISPPARAMS.ByReference pDispParams, VARIANT.ByReference pVarResult, EXCEPINFO.ByReference pExcepInfo, - IntByReference puArgErr) { - this.Invoke_called = true; - return new HRESULT(WinError.E_NOTIMPL); - } - - - //------------------------ IUnknown ------------------------------ - public boolean QueryInterface_called = false; - @Override - public HRESULT QueryInterface(REFIID refid, PointerByReference ppvObject) { - this.QueryInterface_called = true; - if (null==ppvObject) { - return new HRESULT(WinError.E_POINTER); - } - - String s = refid.toGuidString(); - IID appEvnts4 = new IID("{00020A01-0000-0000-C000-000000000046}"); - REFIID riid = new REFIID(appEvnts4.getPointer()); - - if (refid.equals(riid)) { - return WinError.S_OK; - } - - if (new Guid.IID(refid.getPointer()).equals(Unknown.IID_IUNKNOWN)) { - ppvObject.setValue(this.getPointer()); - return WinError.S_OK; - } - - if (new Guid.IID(refid.getPointer()).equals(Dispatch.IID_IDISPATCH)) { - ppvObject.setValue(this.getPointer()); - return WinError.S_OK; - } - - return new HRESULT(WinError.E_NOINTERFACE); - } - - public int AddRef() { - return 0; - } - - public int Release() { - return 0; - } - - } - - @Test - public void Advise() { - - // query for ConnectionPointContainer - Unknown unk = new Unknown(this.ppWordApp.getValue()); - PointerByReference ppCpc = new PointerByReference(); - IID cpcIID = new IID("{B196B284-BAB4-101A-B69C-00AA00341D07}"); - HRESULT hr = unk.QueryInterface(new REFIID(cpcIID), ppCpc); - COMUtils.checkRC(hr); - ConnectionPointContainer cpc = new ConnectionPointContainer(ppCpc.getValue()); - - // find connection point for Application_Events4 - IID appEvnts4 = new IID("{00020A01-0000-0000-C000-000000000046}"); - REFIID riid = new REFIID(appEvnts4.getPointer()); - PointerByReference ppCp = new PointerByReference(); - hr = cpc.FindConnectionPoint(riid, ppCp); - COMUtils.checkRC(hr); - ConnectionPoint cp = new ConnectionPoint(ppCp.getValue()); - IID cp_iid = new IID(); - hr = cp.GetConnectionInterface(cp_iid); - COMUtils.checkRC(hr); - - Application_Events4 listener = new Application_Events4(); - - DWORDByReference pdwCookie = new DWORDByReference(); - hr = cp.Advise(listener, pdwCookie); - COMUtils.checkRC(hr); - - Assert.assertTrue(listener.QueryInterface_called); - - } - -} From 3602338de2f0645f28b625972418766174b885bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Tue, 12 Jan 2016 00:19:06 +0100 Subject: [PATCH 2/5] Make (Un)marshalling context sensitive Depending on the context the unmarshalling of the supplied data in callbacks is unwrapped further than before. The NavigateComplete2 event from the interface DWebBrowserEvents2 demonstrates this. The URL is passed as a Variant pointing to a Variant containing the string. Without this fix NULL is returned. To test this ComEventCallbacks_Test.java was moved to use Internet Explorer instead of MS Office. --- .../win32/COM/util/CallbackProxy.java | 164 +++++---- .../jna/platform/win32/COM/util/Convert.java | 33 +- .../platform/win32/COM/util/ProxyObject.java | 4 +- .../COM/util/ComEventCallbacks_Test.java | 341 ++++++++---------- 4 files changed, 272 insertions(+), 270 deletions(-) diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java index ca73bcd783..7e0316be62 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java @@ -70,7 +70,7 @@ public Thread newThread(Runnable r) { @Override public void uncaughtException(Thread t, Throwable e) { CallbackProxy.this.factory.comThread.uncaughtExceptionHandler.uncaughtException(t, e); - } + } }); return thread; } @@ -121,84 +121,94 @@ int fetchDispIdFromName(ComEventCallback annotation) { } void invokeOnThread(final DISPID dispIdMember, final REFIID riid, LCID lcid, WORD wFlags, - final DISPPARAMS.ByReference pDispParams) { - // decode arguments - // must decode them on this thread, and create a proxy for any COM objects (IDispatch) - // this will AddRef on the COM object so that it is not cleaned up before we can use it - // on the thread that does the java callback. - List rjargs = new ArrayList(); - if (pDispParams.cArgs.intValue() > 0) { - VariantArg vargs = pDispParams.rgvarg; - vargs.setArraySize(pDispParams.cArgs.intValue()); - for (Variant.VARIANT varg : vargs.variantArg) { - Object jarg = Convert.toJavaObject(varg); - if (jarg instanceof IDispatch) { - IDispatch dispatch = (IDispatch) jarg; - //get raw IUnknown interface - PointerByReference ppvObject = new PointerByReference(); - IID iid = com.sun.jna.platform.win32.COM.IUnknown.IID_IUNKNOWN; - dispatch.QueryInterface(new REFIID(iid), ppvObject); - Unknown rawUnk = new Unknown(ppvObject.getValue()); + final DISPPARAMS.ByReference pDispParams) { + + final Method eventMethod; + if (CallbackProxy.this.dsipIdMap.containsKey(dispIdMember)) { + eventMethod = CallbackProxy.this.dsipIdMap.get(dispIdMember); + if (eventMethod.getParameterTypes().length != pDispParams.cArgs.intValue()) { + CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( + "Trying to invoke method " + eventMethod + " with " + pDispParams.cArgs.intValue() + " arguments", + null); + return; + } + } else { + CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( + "No method found with dispId = " + dispIdMember, null); + return; + } + + // decode arguments + // must decode them on this thread, and create a proxy for any COM objects (IDispatch) + // this will AddRef on the COM object so that it is not cleaned up before we can use it + // on the thread that does the java callback. + final Class[] params = eventMethod.getParameterTypes(); + List rjargs = new ArrayList(); + if (pDispParams.cArgs.intValue() > 0) { + VariantArg vargs = pDispParams.rgvarg; + vargs.setArraySize(pDispParams.cArgs.intValue()); + for ( int i = 0; i < vargs.variantArg.length; i++) { + Variant.VARIANT varg = vargs.variantArg[i]; + Object jarg = Convert.toJavaObject(varg, params[vargs.variantArg.length - 1 - i]); + if (jarg instanceof IDispatch) { + IDispatch dispatch = (IDispatch) jarg; + //get raw IUnknown interface + PointerByReference ppvObject = new PointerByReference(); + IID iid = com.sun.jna.platform.win32.COM.IUnknown.IID_IUNKNOWN; + dispatch.QueryInterface(new REFIID(iid), ppvObject); + Unknown rawUnk = new Unknown(ppvObject.getValue()); long unknownId = Pointer.nativeValue( rawUnk.getPointer() ); - int n = rawUnk.Release(); - //Note: unlike in other places, there is currently no COM ref already added for this pointer - IUnknown unk = CallbackProxy.this.factory.createProxy(IUnknown.class, unknownId, dispatch); - rjargs.add(unk); - } else { - rjargs.add(jarg); - } - } - } - final List jargs = new ArrayList(rjargs); - Runnable invokation = new Runnable() { - @Override - public void run() { - try { - if (CallbackProxy.this.dsipIdMap.containsKey(dispIdMember)) { - Method eventMethod = CallbackProxy.this.dsipIdMap.get(dispIdMember); - if (eventMethod.getParameterTypes().length != jargs.size()) { - CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( - "Trying to invoke method " + eventMethod + " with " + jargs.size() + " arguments", - null); - } else { - try { - // need to convert arguments maybe - List margs = new ArrayList(); - Class[] params = eventMethod.getParameterTypes(); - for (int i = 0; i < eventMethod.getParameterTypes().length; ++i) { - Class paramType = params[i]; - Object jobj = jargs.get(i); - if (jobj != null && paramType.getAnnotation(ComInterface.class) != null) { - if (jobj instanceof IUnknown) { - IUnknown unk = (IUnknown) jobj; - Object mobj = unk.queryInterface(paramType); - margs.add(mobj); - } else { - throw new RuntimeException("Cannot convert argument " + jobj.getClass() - + " to ComInterface " + paramType); - } - } else { - margs.add(jobj); - } - } - eventMethod.invoke(comEventCallbackListener, margs.toArray()); - } catch (Exception e) { - CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( - "Exception invoking method " + eventMethod, e); - } - } - } else { - CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( - "No method found with dispId = " + dispIdMember, null); - } - } catch (Exception e) { - CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( - "Exception receiving callback event ", e); - } - } - }; + int n = rawUnk.Release(); + //Note: unlike in other places, there is currently no COM ref already added for this pointer + IUnknown unk = CallbackProxy.this.factory.createProxy(IUnknown.class, unknownId, dispatch); + rjargs.add(unk); + } else { + rjargs.add(jarg); + } + } + } + final List jargs = new ArrayList(rjargs); + Runnable invokation = new Runnable() { + @Override + public void run() { + // need to convert arguments maybe + List margs = new ArrayList(); + try { + // Reverse order from calling convention + int lastParamIdx = eventMethod.getParameterTypes().length - 1; + for (int i = lastParamIdx; i >= 0; i--) { + Class paramType = params[lastParamIdx - i]; + Object jobj = jargs.get(i); + if (jobj != null && paramType.getAnnotation(ComInterface.class) != null) { + if (jobj instanceof IUnknown) { + IUnknown unk = (IUnknown) jobj; + Object mobj = unk.queryInterface(paramType); + margs.add(mobj); + } else { + throw new RuntimeException("Cannot convert argument " + jobj.getClass() + + " to ComInterface " + paramType); + } + } else { + margs.add(jobj); + } + } + eventMethod.invoke(comEventCallbackListener, margs.toArray()); + } catch (Exception e) { + List decodedClassNames = new ArrayList(margs.size()); + for(Object o: margs) { + if(o == null) { + decodedClassNames.add("NULL"); + } else { + decodedClassNames.add(o.getClass().getName()); + } + } + CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( + "Exception invoking method " + eventMethod + " supplied: " + decodedClassNames.toString(), e); + } + } + }; this.executorService.execute(invokation); - } + } @Override public Pointer getPointer() { diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java index 4b0f03a55c..c08686c1ca 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java @@ -25,7 +25,9 @@ public class Convert { public static VARIANT toVariant(Object value) { - if (value instanceof Boolean) { + if (value instanceof VARIANT) { + return (VARIANT) value; + } else if (value instanceof Boolean) { return new VARIANT((Boolean) value); } else if (value instanceof Long) { return new VARIANT(new WinDef.LONG((Long) value)); @@ -45,8 +47,7 @@ public static VARIANT toVariant(Object value) { InvocationHandler ih = Proxy.getInvocationHandler(value); ProxyObject pobj = (ProxyObject) ih; return new VARIANT(pobj.getRawDispatch()); - } - if (value instanceof IComEnum) { + } else if (value instanceof IComEnum) { IComEnum enm = (IComEnum) value; return new VARIANT(new WinDef.LONG(enm.getValue())); } else { @@ -54,10 +55,28 @@ public static VARIANT toVariant(Object value) { } } - public static Object toJavaObject(VARIANT value) { - if (null==value) return null; - Object vobj = value.getValue(); - if (vobj instanceof WinDef.BOOL) { + public static Object toJavaObject(VARIANT value, Class targetClass) { + if (null==value) { + return null; + } + + // Passing null or Object.class as targetClass switch to default + // handling + boolean concreteClassRequested = targetClass != null + && (! targetClass.isAssignableFrom(Object.class)); + + if (concreteClassRequested && targetClass.isAssignableFrom(value.getClass())) { + return value; + } + Object vobj = value.getValue(); + if (vobj != null && concreteClassRequested && targetClass.isAssignableFrom(vobj.getClass())) { + return vobj; + } + // Handle VARIANTByRef + if(vobj instanceof VARIANT) { + vobj = ((VARIANT) vobj).getValue(); + } + if (vobj instanceof WinDef.BOOL) { return ((WinDef.BOOL) vobj).booleanValue(); } else if (vobj instanceof WinDef.LONG) { return ((WinDef.LONG) vobj).longValue(); 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 40a1e64b28..a9223fb4a3 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 @@ -378,7 +378,7 @@ public T getProperty(Class returnType, String name, Object... args) { Variant.VARIANT.ByReference result = new Variant.VARIANT.ByReference(); WinNT.HRESULT hr = this.oleMethod(OleAuto.DISPATCH_PROPERTYGET, result, this.getRawDispatch(), name, vargs); COMUtils.checkRC(hr); - Object jobj = Convert.toJavaObject(result); + Object jobj = Convert.toJavaObject(result, returnType); if (IComEnum.class.isAssignableFrom(returnType)) { return returnType.cast(Convert.toComEnum((Class) returnType, jobj)); } @@ -408,7 +408,7 @@ public T invokeMethod(Class returnType, String name, Object... args) { WinNT.HRESULT hr = this.oleMethod(OleAuto.DISPATCH_METHOD, result, this.getRawDispatch(), name, vargs); COMUtils.checkRC(hr); - Object jobj = Convert.toJavaObject(result); + Object jobj = Convert.toJavaObject(result, returnType); if (IComEnum.class.isAssignableFrom(returnType)) { return returnType.cast(Convert.toComEnum((Class) returnType, jobj)); } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java index cc099a13d1..a49f4b73b7 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java @@ -17,13 +17,16 @@ import org.junit.Before; import org.junit.Test; -import com.sun.jna.platform.win32.User32; -import com.sun.jna.platform.win32.WinDef.HWND; import com.sun.jna.platform.win32.COM.util.annotation.ComEventCallback; 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.COM.util.annotation.ComProperty; +import com.sun.jna.platform.win32.OaIdl; +import com.sun.jna.platform.win32.Variant; +import com.sun.jna.platform.win32.Variant.VARIANT; +import org.hamcrest.CoreMatchers; +import org.junit.Ignore; public class ComEventCallbacks_Test { @@ -37,16 +40,16 @@ public void before() { @After public void after() { this.factory.disposeAll(); - this.factory.getComThread().terminate(100); + this.factory.getComThread().terminate(1000); } - @ComObject(progId="Word.Application") - interface ComIMsWordApp extends ComIApplication { + @ComObject(progId="Internet.Explorer.1", clsId = "{0002DF01-0000-0000-C000-000000000046}") + interface ComInternetExplorer extends ComIWebBrowser2 { } - @ComInterface(iid="{00020970-0000-0000-C000-000000000046}") - interface ComIApplication extends IUnknown, IConnectionPoint { + @ComInterface(iid="{D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}") + interface ComIWebBrowser2 extends IUnknown, IConnectionPoint { @ComProperty boolean getVisible(); @@ -54,215 +57,185 @@ interface ComIApplication extends IUnknown, IConnectionPoint { void setVisible(boolean value); @ComMethod - void Quit(boolean SaveChanges, Object OriginalFormat, Boolean RouteDocument); - - @ComProperty - ComIDocuments getDocuments(); - - } - - @ComInterface(iid="{0002096C-0000-0000-C000-000000000046}") - interface ComIDocuments { - @ComMethod - ComIDocument Open(String fileName); - @ComMethod - ComIDocument Add(); - } - - @ComInterface(iid="{0002096B-0000-0000-C000-000000000046}") - interface ComIDocument { - @ComProperty - String getFullName(); - + void Quit(); + @ComMethod - void Select(); - } - - @ComInterface(iid="{00020962-0000-0000-C000-000000000046}") - interface ComIWindow {} - - @ComInterface(iid="{00020975-0000-0000-C000-000000000046}") - public interface ComISelection { - @ComProperty - String getText(); + /** + * navOpenInNewWindow = 1 + * navNoHistory = 2 + * navNoReadFromCache = 4 + * navNoWriteToCache = 8 + * navAllowAutosearch = 16 + * navBrowserBar = 32 + * navHyperlink = 64 + * navEnforceRestricted = 128 + * navNewWindowsManaged = 256 + * navUntrustedForDownload = 512 + * navTrustedForActiveX = 1024 + * navOpenInNewTab = 2048 + * navOpenInBackgroundTab = 4096 + * navKeepWordWheelText = 8192 + * navVirtualTab = 16384 + * navBlockRedirectsXDomain = 32768 + * navOpenNewForegroundTab = 65536 + */ + void Navigate(String url, long flags, String targetFrameName, VARIANT postData, String headers); } - - @ComInterface(iid="{00020A01-0000-0000-C000-000000000046}") - interface ApplicationEvents4_Event { - @ComEventCallback(dispid=10) - void WindowActivate(ComIDocument doc, ComIWindow win); - - @ComEventCallback(dispid=2) - void Quit(); - - @ComEventCallback(dispid=12) - void WindowSelectionChange(ComISelection sel); + + @ComInterface(iid="{34A715A0-6587-11D0-924A-0020AFC7AC4D}") + interface DWebBrowserEvents2 { + @ComEventCallback(dispid=0x000000fd) + void OnQuit(); + + @ComEventCallback(dispid=0x000000fc) + void NavigateComplete2(IUnknown source, Object url); + + @ComEventCallback(dispid=0x000000fa) + void BeforeNavigate2(IUnknown pDisp, + String URL, + long Flags, + String TargetFrameName, + VARIANT.ByReference PostData, + VARIANT.ByReference Headers, + OaIdl.VARIANT_BOOLByReference Cancel); } - class ApplicationEvents4_EventListener extends AbstractComEventCallbackListener implements ApplicationEvents4_Event { + class DWebBrowserEvents2_Listener extends AbstractComEventCallbackListener implements DWebBrowserEvents2 { @Override public void errorReceivingCallbackEvent(String message, Exception exception) { - - } - - Boolean WindowActivate_called = null; - @Override - public void WindowActivate(ComIDocument doc, ComIWindow win) { - if (null!=doc && null !=win) { - String docName = doc.getFullName(); - WindowActivate_called = true; - } - } - - Boolean Quit_called = null; - @Override - public void Quit() { - Quit_called = true; +// System.err.println(message); +// if(exception != null) { +// System.err.println(exception.getMessage()); +// exception.printStackTrace(System.err); +// } } - Boolean WindowSelectionChange_called = null; + volatile boolean blockNavigate = false; + + public void BeforeNavigate2( + IUnknown pDisp, + String URL, + long Flags, + String TargetFrameName, + VARIANT.ByReference PostData, + VARIANT.ByReference Headers, + OaIdl.VARIANT_BOOLByReference Cancel) { + // This is todo: Event is called not on the event creating + // thread - there are multiple side effects COM demarshalling + // has to happend outside this method and return values + // from event don't work + // + // The utilizing unittest is adviseBeforeNavigate + if(blockNavigate){ + Cancel.setValue(Variant.VARIANT_TRUE); + } + } + + volatile boolean navigateComplete2Called = false; + volatile String navigateComplete2URL = null; + @Override + public void NavigateComplete2( IUnknown source, Object url) { + navigateComplete2Called = true; + if(url != null) { + navigateComplete2URL = url.toString(); + } + } + + volatile Boolean Quit_called = null; @Override - public void WindowSelectionChange(ComISelection sel) { - if (null!=sel) { - String t = sel.getText(); - WindowSelectionChange_called = true; - } - } - + public void OnQuit() { + Quit_called = true; + } } @Test - public void advise_Quit() { - // Create word object - ComIMsWordApp wordObj = factory.createObject(ComIMsWordApp.class); - ComIApplication wordApp = wordObj.queryInterface(ComIApplication.class); - wordApp.setVisible(true); - ApplicationEvents4_EventListener listener = new ApplicationEvents4_EventListener(); - wordApp.advise(ApplicationEvents4_Event.class, listener); + public void advise_Quit() throws InterruptedException { + ComInternetExplorer ieApp = factory.createObject(ComInternetExplorer.class); + ComIWebBrowser2 iWebBrowser2 = ieApp.queryInterface(ComIWebBrowser2.class); + iWebBrowser2.setVisible(true); + DWebBrowserEvents2_Listener listener = new DWebBrowserEvents2_Listener(); + iWebBrowser2.advise(DWebBrowserEvents2.class, listener); - wordApp.Quit(false, null, null); + iWebBrowser2.Quit(); //Wait for event to happen - try { - Thread.sleep(200); - } catch (Exception e) { - e.printStackTrace(); - } + Thread.sleep(200); Assert.assertNotNull(listener.Quit_called); Assert.assertTrue(listener.Quit_called); } @Test - public void unadvise_Quit() { - // Create word object - ComIMsWordApp wordObj = factory.createObject(ComIMsWordApp.class); - ComIApplication wordApp = wordObj.queryInterface(ComIApplication.class); - - ApplicationEvents4_EventListener listener = new ApplicationEvents4_EventListener(); - IComEventCallbackCookie cookie = wordApp.advise(ApplicationEvents4_Event.class, listener); - - wordApp.unadvise(ApplicationEvents4_Event.class, cookie); + public void unadvise_Quit() throws InterruptedException { + ComInternetExplorer ieApp = factory.createObject(ComInternetExplorer.class); + ComIWebBrowser2 iWebBrowser2 = ieApp.queryInterface(ComIWebBrowser2.class); + iWebBrowser2.setVisible(true); + + DWebBrowserEvents2_Listener listener = new DWebBrowserEvents2_Listener(); + IComEventCallbackCookie cookie = iWebBrowser2.advise(DWebBrowserEvents2.class, listener); + + iWebBrowser2.unadvise(DWebBrowserEvents2.class, cookie); listener.Quit_called=false; - wordApp.Quit(false, null, null); + + iWebBrowser2.Quit(); - //Wait for event to happen - try { - Thread.sleep(200); - } catch (Exception e) { - e.printStackTrace(); - } + Thread.sleep(200); Assert.assertNotNull(listener.Quit_called); Assert.assertFalse(listener.Quit_called); } @Test - public void WindowActivate() { - // Create word object - ComIMsWordApp wordObj = factory.createObject(ComIMsWordApp.class); - ComIApplication wordApp = wordObj.queryInterface(ComIApplication.class); - wordApp.setVisible(true); - ApplicationEvents4_EventListener listener = new ApplicationEvents4_EventListener(); - wordApp.advise(ApplicationEvents4_Event.class, listener); - wordApp.getDocuments().Add(); - - //bring word doc to front - HWND h = User32.INSTANCE.FindWindow("OpusApp", null); - if (h == null) - h = User32.INSTANCE.FindWindow("NetUIHWND", null); - User32.INSTANCE.ShowWindow(h, User32.SW_RESTORE); - User32.INSTANCE.SetForegroundWindow(h); - - //Wait for event to happen - try { - Thread.sleep(500); - } catch (Exception e) { - e.printStackTrace(); - } - - Assert.assertNotNull(listener.WindowActivate_called); - Assert.assertTrue(listener.WindowActivate_called); - - wordApp.Quit(false, null, null); - + public void adviseNavigateComplete2() throws InterruptedException { + ComInternetExplorer ieApp = factory.createObject(ComInternetExplorer.class); + ComIWebBrowser2 iWebBrowser2 = ieApp.queryInterface(ComIWebBrowser2.class); + iWebBrowser2.setVisible(true); + + DWebBrowserEvents2_Listener listener = new DWebBrowserEvents2_Listener(); + IComEventCallbackCookie cookie = iWebBrowser2.advise(DWebBrowserEvents2.class, listener); + + iWebBrowser2.Navigate("https://github.com/java-native-access/jna", 0, null, null, null); + + for(int i = 0; i < 10; i++) { + if(listener.navigateComplete2Called) { + break; + } + Thread.sleep(1000); + } + + iWebBrowser2.Quit(); + + Assert.assertTrue("NavigateComplete was not called", listener.navigateComplete2Called); + Assert.assertNotNull("URL passed to NavigateComplete2 was NULL", listener.navigateComplete2URL); + Assert.assertThat(listener.navigateComplete2URL, CoreMatchers.startsWith("https://github.com/java-native-access/jna")); } - + @Test - public void WindowSelectionChanged() { - // Create word object - ComIMsWordApp wordObj = factory.createObject(ComIMsWordApp.class); - ComIApplication wordApp = wordObj.queryInterface(ComIApplication.class); - wordApp.setVisible(true); - ApplicationEvents4_EventListener listener = new ApplicationEvents4_EventListener(); - wordApp.advise(ApplicationEvents4_Event.class, listener); + @Ignore("Known bug - events are currently dispatched out of the event handler, so this fails because return value not reach IE") + public void adviseBeforeNavigate() throws InterruptedException { + ComInternetExplorer ieApp = factory.createObject(ComInternetExplorer.class); + ComIWebBrowser2 iWebBrowser2 = ieApp.queryInterface(ComIWebBrowser2.class); + iWebBrowser2.setVisible(true); + + DWebBrowserEvents2_Listener listener = new DWebBrowserEvents2_Listener(); + IComEventCallbackCookie cookie = iWebBrowser2.advise(DWebBrowserEvents2.class, listener); + + listener.blockNavigate = true; + + iWebBrowser2.Navigate("https://github.com/java-native-access/jna", 0, null, null, null); + + for(int i = 0; i < 10; i++) { + if(listener.navigateComplete2Called) { + break; + } + Thread.sleep(1000); + } + + iWebBrowser2.Quit(); + + // NavigateComplete can't be called if access is blocked + Assert.assertFalse("Navigation to https://github.com/java-native-access/jna should be blocked", listener.navigateComplete2Called); - ComIDocument doc = wordApp.getDocuments().Add(); - - doc.Select(); - - //Wait for event to happen - try { - Thread.sleep(200); - } catch (Exception e) { - e.printStackTrace(); - } - - Assert.assertNotNull(listener.WindowSelectionChange_called); - Assert.assertTrue(listener.WindowSelectionChange_called); - - wordApp.Quit(false, null, null); - } - -// @Test -// public void WindowSelectionChanged_jvmCrash() { -// // Create word object -// ComIMsWordApp wordObj = factory.createObject(ComIMsWordApp.class); -// ComIApplication wordApp = wordObj.queryInterface(ComIApplication.class); -// wordApp.setVisible(true); -// ApplicationEvents4_EventListener listener = new ApplicationEvents4_EventListener(); -// wordApp.advise(ApplicationEvents4_Event.class, listener); -// -// -// -// ComIDocument doc = wordApp.getDocuments().Add(); -// -// doc.Select(); -// -// //Wait for event to happen -// try { -// Thread.sleep(2000000); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// -// Assert.assertNotNull(listener.WindowSelectionChange_called); -// Assert.assertTrue(listener.WindowSelectionChange_called); -// -// wordApp.Quit(false, null, null); -// -// } - } From 334ae3f1de79139606166a05812bd1241ac7d3e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sun, 24 Jan 2016 16:49:04 +0100 Subject: [PATCH 3/5] Move callback invocation in c.s.j.p.w.C.util.CallbackProxy into calling thread Dispatching the invocation of the callback handler into an executor makes it impossible to fill [out] parameters, as the return has already happend and [in] parameters can not be savely used if they are not marshalled to java code because the calling code will free the parameters after the call. To prevent deadlocks ComThread is modified to allow COM calls from the callback by modifying the ComThread helper to only dispatch the COM call into the ComThread only if the calling thread has not COM already enabled. Reference counting was modified, so that now on construction of a ProxyObject the reference count is AddRef'ed once and Released once on finalization. --- .../win32/COM/util/CallbackProxy.java | 107 +++++++----------- .../platform/win32/COM/util/ComThread.java | 38 ++++++- .../jna/platform/win32/COM/util/Factory.java | 79 +++++++------ .../platform/win32/COM/util/ProxyObject.java | 21 ++-- .../COM/util/ComEventCallbacks_Test.java | 6 - 5 files changed, 119 insertions(+), 132 deletions(-) diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java index 7e0316be62..4185e6bf36 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/CallbackProxy.java @@ -12,15 +12,11 @@ */ package com.sun.jna.platform.win32.COM.util; -import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; import com.sun.jna.Pointer; import com.sun.jna.WString; @@ -61,20 +57,6 @@ public CallbackProxy(Factory factory, Class comEventCallbackInterface, this.listenedToRiid = this.createRIID(comEventCallbackInterface); this.dsipIdMap = this.createDispIdMap(comEventCallbackInterface); this.dispatchListener = new DispatchListener(this); - this.executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r, "COM Event Callback executor"); - thread.setDaemon(true); - thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - CallbackProxy.this.factory.comThread.uncaughtExceptionHandler.uncaughtException(t, e); - } - }); - return thread; - } - }); } Factory factory; @@ -83,7 +65,6 @@ public void uncaughtException(Thread t, Throwable e) { REFIID listenedToRiid; public DispatchListener dispatchListener; Map dsipIdMap; - ExecutorService executorService; REFIID createRIID(Class comEventCallbackInterface) { ComInterface comInterfaceAnnotation = comEventCallbackInterface.getAnnotation(ComInterface.class); @@ -138,19 +119,23 @@ void invokeOnThread(final DISPID dispIdMember, final REFIID riid, LCID lcid, WOR return; } - // decode arguments - // must decode them on this thread, and create a proxy for any COM objects (IDispatch) - // this will AddRef on the COM object so that it is not cleaned up before we can use it - // on the thread that does the java callback. + // Arguments are converted to the JAVA side and IDispatch Interfaces + // are wrapped into an ProxyObject if so requested. + // + // Out-Parameter need to be specified as VARIANT, VARIANT args are + // not converted, so COM memory allocation rules apply. final Class[] params = eventMethod.getParameterTypes(); List rjargs = new ArrayList(); if (pDispParams.cArgs.intValue() > 0) { VariantArg vargs = pDispParams.rgvarg; vargs.setArraySize(pDispParams.cArgs.intValue()); for ( int i = 0; i < vargs.variantArg.length; i++) { + Class targetClass = params[vargs.variantArg.length - 1 - i]; Variant.VARIANT varg = vargs.variantArg[i]; - Object jarg = Convert.toJavaObject(varg, params[vargs.variantArg.length - 1 - i]); + Object jarg = Convert.toJavaObject(varg, targetClass); if (jarg instanceof IDispatch) { + // If a dispatch is returned try to wrap it into a proxy + // helper if the target is ComInterface annotated IDispatch dispatch = (IDispatch) jarg; //get raw IUnknown interface PointerByReference ppvObject = new PointerByReference(); @@ -158,56 +143,38 @@ void invokeOnThread(final DISPID dispIdMember, final REFIID riid, LCID lcid, WOR dispatch.QueryInterface(new REFIID(iid), ppvObject); Unknown rawUnk = new Unknown(ppvObject.getValue()); long unknownId = Pointer.nativeValue( rawUnk.getPointer() ); - int n = rawUnk.Release(); - //Note: unlike in other places, there is currently no COM ref already added for this pointer IUnknown unk = CallbackProxy.this.factory.createProxy(IUnknown.class, unknownId, dispatch); - rjargs.add(unk); + if(targetClass.getAnnotation(ComInterface.class) != null) { + rjargs.add(unk.queryInterface(targetClass)); + } else { + rjargs.add(unk); + } } else { rjargs.add(jarg); } } } - final List jargs = new ArrayList(rjargs); - Runnable invokation = new Runnable() { - @Override - public void run() { - // need to convert arguments maybe - List margs = new ArrayList(); - try { - // Reverse order from calling convention - int lastParamIdx = eventMethod.getParameterTypes().length - 1; - for (int i = lastParamIdx; i >= 0; i--) { - Class paramType = params[lastParamIdx - i]; - Object jobj = jargs.get(i); - if (jobj != null && paramType.getAnnotation(ComInterface.class) != null) { - if (jobj instanceof IUnknown) { - IUnknown unk = (IUnknown) jobj; - Object mobj = unk.queryInterface(paramType); - margs.add(mobj); - } else { - throw new RuntimeException("Cannot convert argument " + jobj.getClass() - + " to ComInterface " + paramType); - } - } else { - margs.add(jobj); - } - } - eventMethod.invoke(comEventCallbackListener, margs.toArray()); - } catch (Exception e) { - List decodedClassNames = new ArrayList(margs.size()); - for(Object o: margs) { - if(o == null) { - decodedClassNames.add("NULL"); - } else { - decodedClassNames.add(o.getClass().getName()); - } - } - CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( - "Exception invoking method " + eventMethod + " supplied: " + decodedClassNames.toString(), e); + + List margs = new ArrayList(); + try { + // Reverse order from calling convention + int lastParamIdx = eventMethod.getParameterTypes().length - 1; + for (int i = lastParamIdx; i >= 0; i--) { + margs.add(rjargs.get(i)); + } + eventMethod.invoke(comEventCallbackListener, margs.toArray()); + } catch (Exception e) { + List decodedClassNames = new ArrayList(margs.size()); + for(Object o: margs) { + if(o == null) { + decodedClassNames.add("NULL"); + } else { + decodedClassNames.add(o.getClass().getName()); } } - }; - this.executorService.execute(invokation); + CallbackProxy.this.comEventCallbackListener.errorReceivingCallbackEvent( + "Exception invoking method " + eventMethod + " supplied: " + decodedClassNames.toString(), e); + } } @Override @@ -237,7 +204,13 @@ public HRESULT Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS.ByReference pDispParams, VARIANT.ByReference pVarResult, EXCEPINFO.ByReference pExcepInfo, IntByReference puArgErr) { - this.invokeOnThread(dispIdMember, riid, lcid, wFlags, pDispParams); + assert (! ComThread.getCurrentThreadIsCOM()) : "Assumption about COM threading broken."; + ComThread.setCurrentThreadIsCOM(true); + try { + this.invokeOnThread(dispIdMember, riid, lcid, wFlags, pDispParams); + } finally { + ComThread.setCurrentThreadIsCOM(false); + } return WinError.S_OK; } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ComThread.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ComThread.java index 3ac5c13407..07e6405fd1 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ComThread.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ComThread.java @@ -26,7 +26,6 @@ import com.sun.jna.platform.win32.COM.COMUtils; public class ComThread { - ExecutorService executor; Runnable firstTask; boolean requiresInitialisation; @@ -44,6 +43,8 @@ public ComThread(final String threadName, long timeoutMilliseconds, UncaughtExce this.firstTask = new Runnable() { @Override public void run() { + // By definition this is a COM thread + ComThread.setCurrentThreadIsCOM(true); try { //If we do not use COINIT_MULTITHREADED, it is necessary to have // a message loop see - @@ -119,11 +120,38 @@ protected void finalize() throws Throwable { } } + // The currentThreadIsCOM is used if wrapper are used in a callback + // the callback is called in a new thread by the JNA runtime. As the + // call comes from COM it is asumed, that the thread is correctly + // initialized and can be used for COM calls (see MTA assumption above) + private static ThreadLocal currentThreadIsCOM = new ThreadLocal(); + + static void setCurrentThreadIsCOM(boolean isCOM) { + currentThreadIsCOM.set(isCOM); + } + + static boolean getCurrentThreadIsCOM() { + Boolean res = currentThreadIsCOM.get(); + if(res == null) { + return false; + } else { + return currentThreadIsCOM.get(); + } + } + public T execute(Callable task) throws TimeoutException, InterruptedException, ExecutionException { - if (this.requiresInitialisation) { - executor.execute(firstTask); - } - return executor.submit(task).get(this.timeoutMilliseconds, TimeUnit.MILLISECONDS); + if(getCurrentThreadIsCOM()) { + try { + return task.call(); + } catch (Exception ex) { + throw new ExecutionException(ex); + } + } else { + if (this.requiresInitialisation) { + executor.execute(firstTask); + } + return executor.submit(task).get(this.timeoutMilliseconds, TimeUnit.MILLISECONDS); + } } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java index fe5a6e89c4..ba367b18a8 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Factory.java @@ -13,9 +13,6 @@ package com.sun.jna.platform.win32.COM.util; import java.lang.reflect.Proxy; -import java.util.HashSet; -import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -33,6 +30,11 @@ import com.sun.jna.platform.win32.COM.IDispatch; import com.sun.jna.platform.win32.COM.util.annotation.ComObject; import com.sun.jna.ptr.PointerByReference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; public class Factory { @@ -52,7 +54,6 @@ public void uncaughtException(Thread t, Throwable e) { public Factory(ComThread comThread) { this.comThread = comThread; - this.registeredObjects = new WeakHashMap(); } @Override @@ -232,47 +233,43 @@ public WinNT.HRESULT call() throws Exception { } } - //factory needs to keep a register of all handles to COM objects so that it can clean them up properly - // (if java had an out of scope clean up destructor like C++, this wouldn't be needed) - WeakHashMap registeredObjects; + // Proxy object release their COM interface reference latest in the + // finalize method, which is run when garbadge collection removes the + // object. + // When the factory is finished, the referenced objects loose their + // environment and can't be used anymore. registeredObjects is used + // to dispose interfaces even if garbadge collection has not yet collected + // the proxy objects. + private final List> registeredObjects = new LinkedList>(); public void register(ProxyObject proxyObject) { - synchronized (this.registeredObjects) { - //ProxyObject identity resolves to the underlying native pointer value - // different java ProxyObjects will resolve to the same pointer - // thus we need to count the number of references. - if (this.registeredObjects.containsKey(proxyObject)) { - int r = this.registeredObjects.get(proxyObject); - this.registeredObjects.put(proxyObject, r+1); - } else { - this.registeredObjects.put(proxyObject, 1); - } - } + synchronized (this.registeredObjects) { + this.registeredObjects.add(new WeakReference(proxyObject)); + } } - public void unregister(ProxyObject proxyObject, int d) { - synchronized (this.registeredObjects) { - if (this.registeredObjects.containsKey(proxyObject)) { - int r = this.registeredObjects.get(proxyObject); - if (r > 1) { - this.registeredObjects.put(proxyObject, r-d); - } else { - this.registeredObjects.remove(proxyObject); - } - } else { - throw new RuntimeException("Tried to dispose a ProxyObject that is not registered"); - } - - } - } + public void unregister(ProxyObject proxyObject) { + synchronized (this.registeredObjects) { + Iterator> iterator = this.registeredObjects.iterator(); + while(iterator.hasNext()) { + WeakReference weakRef = iterator.next(); + ProxyObject po = weakRef.get(); + if(po == null || po == proxyObject) { + iterator.remove(); + } + } + } + } public void disposeAll() { - synchronized (this.registeredObjects) { - Set s = new HashSet(this.registeredObjects.keySet()); - for(ProxyObject proxyObject : s) { - int r = this.registeredObjects.get(proxyObject); - proxyObject.dispose(r); - } - this.registeredObjects.clear(); - } + synchronized (this.registeredObjects) { + List> s = new ArrayList>(this.registeredObjects); + for(WeakReference weakRef : s) { + ProxyObject po = weakRef.get(); + if(po != null) { + po.dispose(); + } + } + this.registeredObjects.clear(); + } } } 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 a9223fb4a3..cc47235d2e 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 @@ -60,7 +60,7 @@ */ public class ProxyObject implements InvocationHandler, com.sun.jna.platform.win32.COM.util.IDispatch, IRawDispatchHandle { - + public ProxyObject(Class theInterface, IDispatch rawDispatch, Factory factory) { this.unknownId = -1; this.rawDispatch = rawDispatch; @@ -95,6 +95,7 @@ public ProxyObject(Class theInterface, IDispatch rawDispatch, Factory factory factory.register(this); } + // cached value of the IUnknown interface pointer // Rules of COM state that querying for the IUnknown interface must return // an identical pointer value @@ -137,20 +138,14 @@ public HRESULT call() throws Exception { @Override protected void finalize() throws Throwable { - this.dispose(1); + this.dispose(); } - public void dispose(int r) { - if (((Dispatch) this.rawDispatch).getPointer().equals(Pointer.NULL)) { - // do nothing, already disposed - } else { - for (int i = 0; i < r; ++i) { - // catch result to help with debug - int n = this.rawDispatch.Release(); - int n2 = n; - } - this.factory.unregister(this, r); - ((Dispatch) this.rawDispatch).setPointer(Pointer.NULL); + public synchronized void dispose() { + if (! ((Dispatch) this.rawDispatch).getPointer().equals(Pointer.NULL)) { + this.rawDispatch.Release(); + ((Dispatch) this.rawDispatch).setPointer(Pointer.NULL); + factory.unregister(this); } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java index a49f4b73b7..ea7adc1157 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java @@ -121,11 +121,6 @@ public void BeforeNavigate2( VARIANT.ByReference PostData, VARIANT.ByReference Headers, OaIdl.VARIANT_BOOLByReference Cancel) { - // This is todo: Event is called not on the event creating - // thread - there are multiple side effects COM demarshalling - // has to happend outside this method and return values - // from event don't work - // // The utilizing unittest is adviseBeforeNavigate if(blockNavigate){ Cancel.setValue(Variant.VARIANT_TRUE); @@ -212,7 +207,6 @@ public void adviseNavigateComplete2() throws InterruptedException { } @Test - @Ignore("Known bug - events are currently dispatched out of the event handler, so this fails because return value not reach IE") public void adviseBeforeNavigate() throws InterruptedException { ComInternetExplorer ieApp = factory.createObject(ComInternetExplorer.class); ComIWebBrowser2 iWebBrowser2 = ieApp.queryInterface(ComIWebBrowser2.class); From 78e6d0f7656749d4d7f7855e4db25748fdb720bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sat, 20 Feb 2016 19:27:35 +0100 Subject: [PATCH 4/5] Correct freeing of BSTRs According to MSDN the caller is responsible to free BSTRs. --- .../jna/platform/win32/COM/util/Convert.java | 61 ++++++++++++++++- .../platform/win32/COM/util/ProxyObject.java | 65 ++++++++++--------- .../com/sun/jna/platform/win32/Variant.java | 10 +++ 3 files changed, 105 insertions(+), 31 deletions(-) diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java index c08686c1ca..9a0c707ef5 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/Convert.java @@ -12,6 +12,8 @@ */ package com.sun.jna.platform.win32.COM.util; +import com.sun.jna.platform.win32.OleAuto; +import com.sun.jna.platform.win32.Variant; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -21,9 +23,29 @@ import com.sun.jna.platform.win32.WTypes; import com.sun.jna.platform.win32.WinDef; import com.sun.jna.platform.win32.Variant.VARIANT; +import com.sun.jna.platform.win32.WTypes.BSTR; -public class Convert { - +/** + * This class is considered internal to the package. + */ +class Convert { + /** + * Convert a java value into a VARIANT suitable for passing in a COM + * invocation. + * + *

Implementation notes

+ * + *
    + *
  • VARIANTs are not rewrapped, but passed through unmodified
  • + *
  • A string is wrapped into a BSTR, that is wrapped into the VARIANT. + * The string is allocated as native memory by the VARIANT constructor. + * The BSTR needs to be freed by {@see com.sun.jna.platform.win32.OleAuto#SysFreeString}.
  • + *
+ * + * @see com.sun.jna.platform.win32.Variant.VARIANT#VARIANT(java.lang.String) + * @param value to be wrapped + * @return wrapped VARIANT + */ public static VARIANT toVariant(Object value) { if (value instanceof VARIANT) { return (VARIANT) value; @@ -108,4 +130,39 @@ public static T toComEnum(Class enumType, Object value) } return null; } + + /** + * Free the contents of the supplied VARIANT. + * + *

This method is a companion to {@see #toVariant}. Primary usage is + * to free BSTRs contained in VARIANTs.

+ * + * @param variant to be cleared + * @param javaType type before/after conversion + */ + public static void free(VARIANT variant, Class javaType) { + if(javaType == null) { + return; + } + if(javaType.isAssignableFrom(String.class) + && variant.getVarType().intValue() == Variant.VT_BSTR) { + Object value = variant.getValue(); + if(value instanceof BSTR) { + OleAuto.INSTANCE.SysFreeString((BSTR) value); + } + } + } + + /** + * Free the contents of the supplied VARIANT. + * + *

This method is a companion to {@see #toVariant}. Primary usage is + * to free BSTRs contained in VARIANTs.

+ * + * @param variant to be cleared + * @param value value before/after conversion + */ + public static void free(VARIANT variant, Object value) { + free(variant, value == null ? null : value.getClass()); + } } 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 cc47235d2e..8b91a2e8aa 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 @@ -354,9 +354,10 @@ public HRESULT call() throws Exception { // --------------------- IDispatch ------------------------------ @Override public void setProperty(String name, T value) { - VARIANT v = Convert.toVariant(value); - WinNT.HRESULT hr = this.oleMethod(OleAuto.DISPATCH_PROPERTYPUT, null, this.getRawDispatch(), name, v); - COMUtils.checkRC(hr); + VARIANT v = Convert.toVariant(value); + WinNT.HRESULT hr = this.oleMethod(OleAuto.DISPATCH_PROPERTYPUT, null, this.getRawDispatch(), name, v); + Convert.free(v, value); // Free value allocated by Convert#toVariant + COMUtils.checkRC(hr); } @Override @@ -372,20 +373,15 @@ public T getProperty(Class returnType, String name, Object... args) { } Variant.VARIANT.ByReference result = new Variant.VARIANT.ByReference(); WinNT.HRESULT hr = this.oleMethod(OleAuto.DISPATCH_PROPERTYGET, result, this.getRawDispatch(), name, vargs); + + for (int i = 0; i < vargs.length; i++) { + // Free value allocated by Convert#toVariant + Convert.free(vargs[i], args[i]); + } + COMUtils.checkRC(hr); - Object jobj = Convert.toJavaObject(result, returnType); - if (IComEnum.class.isAssignableFrom(returnType)) { - return returnType.cast(Convert.toComEnum((Class) returnType, jobj)); - } - if (jobj instanceof IDispatch) { - IDispatch d = (IDispatch) jobj; - T t = this.factory.createProxy(returnType, d); - // must release a COM reference, createProxy adds one, as does the - // call - int n = d.Release(); - return t; - } - return returnType.cast(jobj); + + return convertAndFreeReturn(result, returnType); } @Override @@ -401,23 +397,34 @@ public T invokeMethod(Class returnType, String name, Object... args) { } Variant.VARIANT.ByReference result = new Variant.VARIANT.ByReference(); WinNT.HRESULT hr = this.oleMethod(OleAuto.DISPATCH_METHOD, result, this.getRawDispatch(), name, vargs); + + for (int i = 0; i < vargs.length; i++) { + // Free value allocated by Convert#toVariant + Convert.free(vargs[i], args[i]); + } + COMUtils.checkRC(hr); - Object jobj = Convert.toJavaObject(result, returnType); - if (IComEnum.class.isAssignableFrom(returnType)) { - return returnType.cast(Convert.toComEnum((Class) returnType, jobj)); - } - if (jobj instanceof IDispatch) { - IDispatch d = (IDispatch) jobj; - T t = this.factory.createProxy(returnType, d); - // must release a COM reference, createProxy adds one, as does the - // call - int n = d.Release(); - return t; - } - return returnType.cast(jobj); + return convertAndFreeReturn(result, returnType); } + private T convertAndFreeReturn(VARIANT.ByReference result, Class returnType) { + Object jobj = Convert.toJavaObject(result, returnType); + if (IComEnum.class.isAssignableFrom(returnType)) { + return returnType.cast(Convert.toComEnum((Class) returnType, jobj)); + } else if (jobj instanceof IDispatch) { + IDispatch d = (IDispatch) jobj; + T t = this.factory.createProxy(returnType, d); + // must release a COM reference, createProxy adds one, as does the + // call + int n = d.Release(); + return t; + } else { + Convert.free(result, returnType); + return returnType.cast(jobj); + } + } + @Override public T queryInterface(Class comInterface) throws COMException { try { diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Variant.java b/contrib/platform/src/com/sun/jna/platform/win32/Variant.java index a3efa4f02f..e6f8c790a3 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Variant.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Variant.java @@ -234,6 +234,16 @@ public VARIANT(double value) { this.setValue(VT_R8, value); } + /** + * Create a new VARIANT wrapping the supplied string. + * + *

Implementation note: the string is wrapped as a BSTR value, + * that is allocated using {@see com.sun.jna.platform.win32.OleAuto#SysAllocString} + * and needs to be freed using + * {@see com.sun.jna.platform.win32.OleAuto#SysFreeString} by the user

+ * + * @param value to be wrapped + */ public VARIANT(String value) { this(); BSTR bstrValue = OleAuto.INSTANCE.SysAllocString(value); From ad12bf7af23f227396a6afe364341229f584f642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Tue, 12 Jan 2016 15:27:39 +0100 Subject: [PATCH 5/5] Added CHANGES.md entry --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 813d9413ff..db4be767d3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -48,6 +48,7 @@ Bug Fixes * [#584](https://github.com/java-native-access/jna/pull/584): Promote float varargs to double - [@marco2357](https://github.com/marco2357). * [#588](https://github.com/java-native-access/jna/pull/588): Fix varargs calls on arm - [@twall](https://github.com/twall). * [#593](https://github.com/java-native-access/jna/pull/593): Improve binding of TypeLib bindings - [@matthiasblaesing](https://github.com/matthiasblaesing). +* [#578](https://github.com/java-native-access/jna/pull/578): Fix COM CallbackHandlers, allow usage of VARIANTs directly in c.s.j.p.w.COM.util.ProxyObject and fix native memory leak in c.s.j.p.w.COM.util.ProxyObject - [@matthiasblaesing](https://github.com/matthiasblaesing) Release 4.2.1 =============