From bcf57cdd195066eaeaa6b3fd9a65e11ba8cabeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sun, 8 May 2016 16:36:03 +0200 Subject: [PATCH] Reintroduce ComThread and move base functions into ObjectFactory The removal of the ComThread broke one use case of the win32.com.util packages. It was now necessary to explicitly handle threading and COM initialization. The intention was to broaden the possible use cases of the win32.com.util package. This changeset tries to reinstantiate the old functionality while keeping the benefits of the intended change by introducing a new baseclass. --- .../win32/COM/util/CallbackProxy.java | 4 +- .../platform/win32/COM/util/ComThread.java | 150 ++++++++ .../jna/platform/win32/COM/util/Convert.java | 2 +- .../platform/win32/COM/util/EnumMoniker.java | 7 +- .../jna/platform/win32/COM/util/Factory.java | 348 ++++++++---------- .../win32/COM/util/ObjectFactory.java | 233 ++++++++++++ .../platform/win32/COM/util/ProxyObject.java | 7 +- .../win32/COM/util/RunningObjectTable.java | 7 +- .../platform/win32/COM/EnumMoniker_Test.java | 6 +- .../util/ComEventCallbacksFactory_Test.java | 292 +++++++++++++++ ... ComEventCallbacksObjectFactory_Test.java} | 6 +- .../platform/win32/COM/util/ConvertTest.java | 4 +- .../COM/util/HybdridCOMInvocationTest.java | 2 +- .../win32/COM/util/IDispatchTest.java | 4 +- .../COM/util/ProxyObjectFactory_Test.java | 188 ++++++++++ ...ava => ProxyObjectObjectFactory_Test.java} | 6 +- .../COM/util/RunningObjectTable_Test.java | 4 +- .../sun/jna/platform/win32/SAFEARRAYTest.java | 7 +- 18 files changed, 1037 insertions(+), 240 deletions(-) create mode 100644 contrib/platform/src/com/sun/jna/platform/win32/COM/util/ComThread.java create mode 100644 contrib/platform/src/com/sun/jna/platform/win32/COM/util/ObjectFactory.java create mode 100644 contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacksFactory_Test.java rename contrib/platform/test/com/sun/jna/platform/win32/COM/util/{ComEventCallbacks_Test.java => ComEventCallbacksObjectFactory_Test.java} (96%) create mode 100644 contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java rename contrib/platform/test/com/sun/jna/platform/win32/COM/util/{ProxyObject_Test.java => ProxyObjectObjectFactory_Test.java} (94%) 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 f675aaa273..64b5e8177b 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 @@ -55,7 +55,7 @@ public class CallbackProxy implements IDispatchCallback { private static float DEFAULT_FLOAT; private static double DEFAULT_DOUBLE; - public CallbackProxy(Factory factory, Class comEventCallbackInterface, + public CallbackProxy(ObjectFactory factory, Class comEventCallbackInterface, IComEventCallbackListener comEventCallbackListener) { this.factory = factory; this.comEventCallbackInterface = comEventCallbackInterface; @@ -65,7 +65,7 @@ public CallbackProxy(Factory factory, Class comEventCallbackInterface, this.dispatchListener = new DispatchListener(this); } - Factory factory; + ObjectFactory factory; Class comEventCallbackInterface; IComEventCallbackListener comEventCallbackListener; REFIID listenedToRiid; 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 new file mode 100644 index 0000000000..3d2e868e01 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ComThread.java @@ -0,0 +1,150 @@ +/* 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.util; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.sun.jna.platform.win32.Ole32; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.platform.win32.COM.COMUtils; + +public class ComThread { + private static ThreadLocal isCOMThread = new ThreadLocal(); + + ExecutorService executor; + Runnable firstTask; + boolean requiresInitialisation; + long timeoutMilliseconds; + UncaughtExceptionHandler uncaughtExceptionHandler; + + public ComThread(final String threadName, long timeoutMilliseconds, UncaughtExceptionHandler uncaughtExceptionHandler) { + this(threadName, timeoutMilliseconds, uncaughtExceptionHandler, Ole32.COINIT_MULTITHREADED); + } + + public ComThread(final String threadName, long timeoutMilliseconds, UncaughtExceptionHandler uncaughtExceptionHandler, final int coinitialiseExFlag) { + this.requiresInitialisation = true; + this.timeoutMilliseconds = timeoutMilliseconds; + this.uncaughtExceptionHandler = uncaughtExceptionHandler; + this.firstTask = new Runnable() { + @Override + public void run() { + try { + //If we do not use COINIT_MULTITHREADED, it is necessary to have + // a message loop see - + // [http://www.codeguru.com/cpp/com-tech/activex/apts/article.php/c5529/Understanding-COM-Apartments-Part-I.htm] + // [http://www.codeguru.com/cpp/com-tech/activex/apts/article.php/c5533/Understanding-COM-Apartments-Part-II.htm] + WinNT.HRESULT hr = Ole32.INSTANCE.CoInitializeEx(null, coinitialiseExFlag); + isCOMThread.set(true); + COMUtils.checkRC(hr); + ComThread.this.requiresInitialisation = false; + } catch (Throwable t) { + ComThread.this.uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), t); + } + } + }; + executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + if (!ComThread.this.requiresInitialisation) { + // something has gone wrong! + throw new RuntimeException("ComThread executor has a problem."); + } + Thread thread = new Thread(r, threadName); + //make sure this is a daemon thread, or it will stop JVM existing + // if program does not call terminate(); + thread.setDaemon(true); + + thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + ComThread.this.requiresInitialisation = true; + ComThread.this.uncaughtExceptionHandler.uncaughtException(t, e); + } + }); + + return thread; + } + }); + + } + + /** + * Stop the COM Thread. + * + * @param timeoutMilliseconds + * number of milliseconds to wait for a clean shutdown before a + * forced shutdown is attempted + */ + public void terminate(long timeoutMilliseconds) { + try { + + executor.submit(new Runnable() { + @Override + public void run() { + Ole32.INSTANCE.CoUninitialize(); + } + }).get(timeoutMilliseconds, TimeUnit.MILLISECONDS); + + executor.shutdown(); + + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (TimeoutException e) { + executor.shutdownNow(); + } + } + + @Override + protected void finalize() throws Throwable { + if (!executor.isShutdown()) { + this.terminate(100); + } + } + + static void setComThread(boolean value) { + isCOMThread.set(value); + } + + public T execute(Callable task) throws TimeoutException, InterruptedException, ExecutionException { + // If the call is done on a COM thread, invoke directly + // if the call comes from outside the invokation is dispatched + // into the Dispatch Thread. + Boolean comThread = isCOMThread.get(); + if(comThread == null) { + comThread = false; + } + if(comThread) { + 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); + } + } + +} \ No newline at end of file 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 adc1003e19..2e97834069 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 @@ -133,7 +133,7 @@ public static VARIANT toVariant(Object value) { } } - public static Object toJavaObject(VARIANT value, Class targetClass, Factory factory, boolean addReference, boolean freeValue) { + public static Object toJavaObject(VARIANT value, Class targetClass, ObjectFactory factory, boolean addReference, boolean freeValue) { if (null==value || value.getVarType().intValue() == VT_EMPTY || value.getVarType().intValue() == VT_NULL) { diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/EnumMoniker.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/EnumMoniker.java index dc66f5192c..6483b0819a 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/EnumMoniker.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/EnumMoniker.java @@ -13,9 +13,6 @@ package com.sun.jna.platform.win32.COM.util; import java.util.Iterator; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; import com.sun.jna.platform.win32.WinDef; import com.sun.jna.platform.win32.WinNT; @@ -36,7 +33,7 @@ public class EnumMoniker implements Iterable { protected EnumMoniker(IEnumMoniker raw, com.sun.jna.platform.win32.COM.IRunningObjectTable rawRot, - Factory factory) { + ObjectFactory factory) { assert COMUtils.comIsInitialized() : "COM not initialized"; @@ -50,7 +47,7 @@ protected EnumMoniker(IEnumMoniker raw, com.sun.jna.platform.win32.COM.IRunningO this.cacheNext(); } - Factory factory; + ObjectFactory factory; com.sun.jna.platform.win32.COM.IRunningObjectTable rawRot; IEnumMoniker raw; Moniker rawNext; 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 0e16b7c55d..79256cd19c 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 @@ -10,223 +10,163 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ -package com.sun.jna.platform.win32.COM.util; -import java.lang.reflect.Proxy; +package com.sun.jna.platform.win32.COM.util; -import com.sun.jna.platform.win32.Guid.CLSID; -import com.sun.jna.platform.win32.Guid.GUID; -import com.sun.jna.platform.win32.Ole32; +import com.sun.jna.platform.win32.COM.IDispatch; +import com.sun.jna.platform.win32.COM.IDispatchCallback; +import com.sun.jna.platform.win32.COM.util.annotation.ComObject; +import com.sun.jna.platform.win32.Guid; +import com.sun.jna.platform.win32.OaIdl; import com.sun.jna.platform.win32.OleAuto; -import com.sun.jna.platform.win32.WTypes; +import com.sun.jna.platform.win32.Variant; import com.sun.jna.platform.win32.WinDef; import com.sun.jna.platform.win32.WinNT; -import com.sun.jna.platform.win32.COM.COMException; -import com.sun.jna.platform.win32.COM.COMUtils; -import com.sun.jna.platform.win32.COM.Dispatch; -import com.sun.jna.platform.win32.COM.IDispatch; -import com.sun.jna.platform.win32.COM.util.annotation.ComObject; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinDef.LCID; -import com.sun.jna.platform.win32.WinNT.HRESULT; -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 { - /** - * Factory keeps track of COM objects - all objects created with this - * factory can be disposed by calling {@link Factory#disposeAll() }. - */ - public Factory() { - assert COMUtils.comIsInitialized() : "COM not initialized"; - } - - @Override - protected void finalize() throws Throwable { - try { - this.disposeAll(); - } finally { - super.finalize(); - } - } - - /** - * CoInitialize must be called be fore this method. Either explicitly or - * implicitly via other methods. - * - * @return running object table - */ - public IRunningObjectTable getRunningObjectTable() { - assert COMUtils.comIsInitialized() : "COM not initialized"; - - final PointerByReference rotPtr = new PointerByReference(); - - HRESULT hr = Ole32.INSTANCE.GetRunningObjectTable(new WinDef.DWORD(0), rotPtr); - - COMUtils.checkRC(hr); - com.sun.jna.platform.win32.COM.RunningObjectTable raw = new com.sun.jna.platform.win32.COM.RunningObjectTable( - rotPtr.getValue()); - IRunningObjectTable rot = new RunningObjectTable(raw, this); - return rot; - } +import com.sun.jna.ptr.IntByReference; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +/** + * Factory is intended as a simpler to use version of ObjectFactory. + * + *

The Factory abstracts the necessity to handle COM threading by introducing + * a dispatching thread, that is correctly COM initialized and is used to handle + * all outgoing calls.

+ * + *

NOTE: Remember to call factory.getComThread().terminate() at some + * appropriate point, when the factory is not used anymore

+ */ +public class Factory extends ObjectFactory { - /** - * Creates a ProxyObject for the given interface and IDispatch pointer. - * - */ - public T createProxy(Class comInterface, IDispatch dispatch) { - assert COMUtils.comIsInitialized() : "COM not initialized"; - - ProxyObject jop = new ProxyObject(comInterface, dispatch, this); - Object proxy = Proxy.newProxyInstance(comInterface.getClassLoader(), new Class[] { comInterface }, jop); - T result = comInterface.cast(proxy); - return result; - } - - /** - * Creates a new COM object (CoCreateInstance) for the given progId and - * returns a ProxyObject for the given interface. - */ - public T createObject(Class comInterface) { - assert COMUtils.comIsInitialized() : "COM not initialized"; - - ComObject comObectAnnotation = comInterface.getAnnotation(ComObject.class); - if (null == comObectAnnotation) { - throw new COMException( - "createObject: Interface must define a value for either clsId or progId via the ComInterface annotation"); - } - final GUID guid = this.discoverClsId(comObectAnnotation); - - final PointerByReference ptrDisp = new PointerByReference(); - WinNT.HRESULT hr = Ole32.INSTANCE.CoCreateInstance(guid, null, - WTypes.CLSCTX_SERVER, IDispatch.IID_IDISPATCH, ptrDisp); - - COMUtils.checkRC(hr); - Dispatch d = new Dispatch(ptrDisp.getValue()); - T t = this.createProxy(comInterface,d); - //CoCreateInstance returns a pointer to COM object with a +1 reference count, so we must drop one - //Note: the createProxy adds one - int n = d.Release(); - return t; - } - - /** - * Gets and existing COM object (GetActiveObject) for the given progId and - * returns a ProxyObject for the given interface. - */ - public T fetchObject(Class comInterface) { - assert COMUtils.comIsInitialized() : "COM not initialized"; - - ComObject comObectAnnotation = comInterface.getAnnotation(ComObject.class); - if (null == comObectAnnotation) { - throw new COMException( - "createObject: Interface must define a value for either clsId or progId via the ComInterface annotation"); - } - final GUID guid = this.discoverClsId(comObectAnnotation); - - final PointerByReference ptrDisp = new PointerByReference(); - WinNT.HRESULT hr = OleAuto.INSTANCE.GetActiveObject(guid, null, ptrDisp); - - COMUtils.checkRC(hr); - Dispatch d = new Dispatch(ptrDisp.getValue()); - T t = this.createProxy(comInterface, d); - //GetActiveObject returns a pointer to COM object with a +1 reference count, so we must drop one - //Note: the createProxy adds one - d.Release(); - - return t; - } - - GUID discoverClsId(ComObject annotation) { - assert COMUtils.comIsInitialized() : "COM not initialized"; - - String clsIdStr = annotation.clsId(); - final String progIdStr = annotation.progId(); - if (null != clsIdStr && !clsIdStr.isEmpty()) { - return new CLSID(clsIdStr); - } else if (null != progIdStr && !progIdStr.isEmpty()) { - final CLSID.ByReference rclsid = new CLSID.ByReference(); - - WinNT.HRESULT hr = Ole32.INSTANCE.CLSIDFromProgID(progIdStr, rclsid); - - COMUtils.checkRC(hr); - return rclsid; - } else { - throw new COMException("ComObject must define a value for either clsId or progId"); - } - } - - // 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) { - this.registeredObjects.add(new WeakReference(proxyObject)); + private ComThread comThread; + + public Factory() { + this(new ComThread("Default Factory COM Thread", 5000, new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + //ignore } - } - - 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 Factory(ComThread comThread) { + this.comThread = comThread; + } + + private class ProxyObject2 implements InvocationHandler { + + private final Object delegate; + + public ProxyObject2(Object delegate) { + this.delegate = delegate; + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + if (args != null) { + for (int i = 0; i < args.length; i++) { + if (args[i] != null + && Proxy.isProxyClass(args[i].getClass())) { + InvocationHandler ih = Proxy.getInvocationHandler(args[i]); + if (ih instanceof ProxyObject2) { + args[i] = ((ProxyObject2) ih).delegate; + } } } } - } - - public void disposeAll() { - synchronized (this.registeredObjects) { - List> s = new ArrayList>(this.registeredObjects); - for(WeakReference weakRef : s) { - ProxyObject po = weakRef.get(); - if(po != null) { - po.dispose(); - } + + return comThread.execute(new Callable() { + @Override + public Object call() throws Exception { + return method.invoke(delegate, args); } - this.registeredObjects.clear(); + }); + } + } + + private class CallbackProxy2 extends CallbackProxy { + + public CallbackProxy2(ObjectFactory factory, Class comEventCallbackInterface, IComEventCallbackListener comEventCallbackListener) { + super(factory, comEventCallbackInterface, comEventCallbackListener); + } + + @Override + public WinNT.HRESULT Invoke(OaIdl.DISPID dispIdMember, Guid.REFIID riid, WinDef.LCID lcid, WinDef.WORD wFlags, OleAuto.DISPPARAMS.ByReference pDispParams, Variant.VARIANT.ByReference pVarResult, OaIdl.EXCEPINFO.ByReference pExcepInfo, IntByReference puArgErr) { + // Mark callbacks as COM initialized - so normal inline call + // invocation can be used -- see ComThread# + ComThread.setComThread(true); + try { + return super.Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); + } finally { + ComThread.setComThread(false); } - } - - /** - * The Constant LOCALE_USER_DEFAULT. - */ - private final static LCID LOCALE_USER_DEFAULT = Kernel32.INSTANCE.GetUserDefaultLCID(); + } + } - private LCID LCID; - - /** - * Retrieve the LCID to be used for COM calls. - * - * @return If {@code setLCID} is not called retrieves the users default - * locale, else the set LCID. - */ - public LCID getLCID() { - if(LCID != null) { - return LCID; - } else { - return LOCALE_USER_DEFAULT; + @Override + public T createProxy(Class comInterface, IDispatch dispatch) { + T result = super.createProxy(comInterface, dispatch); + ProxyObject2 po2 = new ProxyObject2(result); + Object proxy = Proxy.newProxyInstance(comInterface.getClassLoader(), new Class[]{comInterface}, po2); + return (T) proxy; + } + + @Override + Guid.GUID discoverClsId(final ComObject annotation) { + return runInComThread(new Callable() { + public Guid.GUID call() throws Exception { + return Factory.super.discoverClsId(annotation); } + }); + } + + @Override + public T fetchObject(final Class comInterface) { + // Proxy2 is added by createProxy inside fetch Object + return runInComThread(new Callable() { + public T call() throws Exception { + return Factory.super.fetchObject(comInterface); + } + }); + } + + @Override + public T createObject(final Class comInterface) { + // Proxy2 is added by createProxy inside fetch Object + return runInComThread(new Callable() { + public T call() throws Exception { + return Factory.super.createObject(comInterface); + } + }); + } + + @Override + IDispatchCallback createDispatchCallback(Class comEventCallbackInterface, IComEventCallbackListener comEventCallbackListener) { + return new CallbackProxy2(this, comEventCallbackInterface, comEventCallbackListener); + } + + @Override + public IRunningObjectTable getRunningObjectTable() { + return super.getRunningObjectTable(); + } + + private T runInComThread(Callable callable) { + try { + return comThread.execute(callable); + } catch (TimeoutException ex) { + throw new RuntimeException(ex); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } catch (ExecutionException ex) { + throw new RuntimeException(ex); } - - /** - * Set the LCID to use for COM calls. - * - * @param value override LCID. NULL resets to default. - */ - public void setLCID(LCID value) { - LCID = value; - } + } + + public ComThread getComThread() { + return comThread; + } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ObjectFactory.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ObjectFactory.java new file mode 100644 index 0000000000..a3b2260d20 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/ObjectFactory.java @@ -0,0 +1,233 @@ +/* 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.util; + +import java.lang.reflect.Proxy; + +import com.sun.jna.platform.win32.Guid.CLSID; +import com.sun.jna.platform.win32.Guid.GUID; +import com.sun.jna.platform.win32.Ole32; +import com.sun.jna.platform.win32.OleAuto; +import com.sun.jna.platform.win32.WTypes; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.platform.win32.COM.COMException; +import com.sun.jna.platform.win32.COM.COMUtils; +import com.sun.jna.platform.win32.COM.Dispatch; +import com.sun.jna.platform.win32.COM.IDispatch; +import com.sun.jna.platform.win32.COM.IDispatchCallback; +import com.sun.jna.platform.win32.COM.util.annotation.ComObject; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinDef.LCID; +import com.sun.jna.platform.win32.WinNT.HRESULT; +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; + +/** + * Factory keeps track of COM objects - all objects created with this factory + * can be disposed by calling {@link Factory#disposeAll() }. + */ +public class ObjectFactory { + @Override + protected void finalize() throws Throwable { + try { + this.disposeAll(); + } finally { + super.finalize(); + } + } + + /** + * CoInitialize must be called be fore this method. Either explicitly or + * implicitly via other methods. + * + * @return running object table + */ + public IRunningObjectTable getRunningObjectTable() { + assert COMUtils.comIsInitialized() : "COM not initialized"; + + final PointerByReference rotPtr = new PointerByReference(); + + HRESULT hr = Ole32.INSTANCE.GetRunningObjectTable(new WinDef.DWORD(0), rotPtr); + + COMUtils.checkRC(hr); + com.sun.jna.platform.win32.COM.RunningObjectTable raw = new com.sun.jna.platform.win32.COM.RunningObjectTable( + rotPtr.getValue()); + IRunningObjectTable rot = new RunningObjectTable(raw, this); + return rot; + } + + /** + * Creates a ProxyObject for the given interface and IDispatch pointer. + * + */ + public T createProxy(Class comInterface, IDispatch dispatch) { + assert COMUtils.comIsInitialized() : "COM not initialized"; + + ProxyObject jop = new ProxyObject(comInterface, dispatch, this); + Object proxy = Proxy.newProxyInstance(comInterface.getClassLoader(), new Class[] { comInterface }, jop); + T result = comInterface.cast(proxy); + return result; + } + + /** + * Creates a new COM object (CoCreateInstance) for the given progId and + * returns a ProxyObject for the given interface. + */ + public T createObject(Class comInterface) { + assert COMUtils.comIsInitialized() : "COM not initialized"; + + ComObject comObectAnnotation = comInterface.getAnnotation(ComObject.class); + if (null == comObectAnnotation) { + throw new COMException( + "createObject: Interface must define a value for either clsId or progId via the ComInterface annotation"); + } + final GUID guid = this.discoverClsId(comObectAnnotation); + + final PointerByReference ptrDisp = new PointerByReference(); + WinNT.HRESULT hr = Ole32.INSTANCE.CoCreateInstance(guid, null, + WTypes.CLSCTX_SERVER, IDispatch.IID_IDISPATCH, ptrDisp); + + COMUtils.checkRC(hr); + Dispatch d = new Dispatch(ptrDisp.getValue()); + T t = this.createProxy(comInterface,d); + //CoCreateInstance returns a pointer to COM object with a +1 reference count, so we must drop one + //Note: the createProxy adds one + int n = d.Release(); + return t; + } + + /** + * Gets and existing COM object (GetActiveObject) for the given progId and + * returns a ProxyObject for the given interface. + */ + public T fetchObject(Class comInterface) { + assert COMUtils.comIsInitialized() : "COM not initialized"; + + ComObject comObectAnnotation = comInterface.getAnnotation(ComObject.class); + if (null == comObectAnnotation) { + throw new COMException( + "createObject: Interface must define a value for either clsId or progId via the ComInterface annotation"); + } + final GUID guid = this.discoverClsId(comObectAnnotation); + + final PointerByReference ptrDisp = new PointerByReference(); + WinNT.HRESULT hr = OleAuto.INSTANCE.GetActiveObject(guid, null, ptrDisp); + + COMUtils.checkRC(hr); + Dispatch d = new Dispatch(ptrDisp.getValue()); + T t = this.createProxy(comInterface, d); + //GetActiveObject returns a pointer to COM object with a +1 reference count, so we must drop one + //Note: the createProxy adds one + d.Release(); + + return t; + } + + GUID discoverClsId(ComObject annotation) { + assert COMUtils.comIsInitialized() : "COM not initialized"; + + String clsIdStr = annotation.clsId(); + final String progIdStr = annotation.progId(); + if (null != clsIdStr && !clsIdStr.isEmpty()) { + return new CLSID(clsIdStr); + } else if (null != progIdStr && !progIdStr.isEmpty()) { + final CLSID.ByReference rclsid = new CLSID.ByReference(); + + WinNT.HRESULT hr = Ole32.INSTANCE.CLSIDFromProgID(progIdStr, rclsid); + + COMUtils.checkRC(hr); + return rclsid; + } else { + throw new COMException("ComObject must define a value for either clsId or progId"); + } + } + + IDispatchCallback createDispatchCallback(Class comEventCallbackInterface, IComEventCallbackListener comEventCallbackListener) { + return new CallbackProxy(this, comEventCallbackInterface, comEventCallbackListener); + } + + // 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) { + this.registeredObjects.add(new WeakReference(proxyObject)); + } + } + + 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) { + List> s = new ArrayList>(this.registeredObjects); + for(WeakReference weakRef : s) { + ProxyObject po = weakRef.get(); + if(po != null) { + po.dispose(); + } + } + this.registeredObjects.clear(); + } + } + + /** + * The Constant LOCALE_USER_DEFAULT. + */ + private final static LCID LOCALE_USER_DEFAULT = Kernel32.INSTANCE.GetUserDefaultLCID(); + + private LCID LCID; + + /** + * Retrieve the LCID to be used for COM calls. + * + * @return If {@code setLCID} is not called retrieves the users default + * locale, else the set LCID. + */ + public LCID getLCID() { + if(LCID != null) { + return LCID; + } else { + return LOCALE_USER_DEFAULT; + } + } + + /** + * Set the LCID to use for COM calls. + * + * @param value override LCID. NULL resets to default. + */ + public void setLCID(LCID value) { + LCID = value; + } +} 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 47bfce62eb..6d0f1f531a 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 @@ -71,10 +71,10 @@ public class ProxyObject implements InvocationHandler, com.sun.jna.platform.win3 // an identical pointer value private long unknownId; private final Class theInterface; - private final Factory factory; + private final ObjectFactory factory; private final com.sun.jna.platform.win32.COM.IDispatch rawDispatch; - public ProxyObject(Class theInterface, IDispatch rawDispatch, Factory factory) { + public ProxyObject(Class theInterface, IDispatch rawDispatch, ObjectFactory factory) { this.unknownId = -1; this.rawDispatch = rawDispatch; this.theInterface = theInterface; @@ -276,8 +276,7 @@ public IComEventCallbackCookie advise(Class comEventCallbackInterface, final ConnectionPoint rawCp = this.fetchRawConnectionPoint(iid); // create the dispatch listener - final IDispatchCallback rawListener = new CallbackProxy(this.factory, comEventCallbackInterface, - comEventCallbackListener); + final IDispatchCallback rawListener = factory.createDispatchCallback(comEventCallbackInterface, comEventCallbackListener); // store it the comEventCallback argument, so it is not garbage // collected. comEventCallbackListener.setDispatchCallbackListener(rawListener); diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/RunningObjectTable.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/RunningObjectTable.java index acbcde1c69..e2ae3aeb89 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/RunningObjectTable.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/RunningObjectTable.java @@ -14,9 +14,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; import com.sun.jna.platform.win32.WinNT; import com.sun.jna.platform.win32.COM.COMException; @@ -25,12 +22,12 @@ public class RunningObjectTable implements IRunningObjectTable { - protected RunningObjectTable(com.sun.jna.platform.win32.COM.RunningObjectTable raw, Factory factory) { + protected RunningObjectTable(com.sun.jna.platform.win32.COM.RunningObjectTable raw, ObjectFactory factory) { this.raw = raw; this.factory = factory; } - Factory factory; + ObjectFactory factory; com.sun.jna.platform.win32.COM.RunningObjectTable raw; @Override diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/EnumMoniker_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/EnumMoniker_Test.java index 3f74948a82..c2e4d1aa45 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/EnumMoniker_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/EnumMoniker_Test.java @@ -26,7 +26,7 @@ import com.sun.jna.platform.win32.WinDef.ULONG; import com.sun.jna.platform.win32.WinDef.ULONGByReference; import com.sun.jna.platform.win32.WinNT.HRESULT; -import com.sun.jna.platform.win32.COM.util.Factory; +import com.sun.jna.platform.win32.COM.util.ObjectFactory; 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; @@ -54,7 +54,7 @@ interface Application { interface MsWordApp extends Application { } - Factory factory; + ObjectFactory factory; MsWordApp ob1; MsWordApp ob2; @@ -63,7 +63,7 @@ public void before() { WinNT.HRESULT hr = Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED); COMUtils.checkRC(hr); - this.factory = new Factory(); + this.factory = new ObjectFactory(); // Two COM objects are require to be running for these tests to work this.ob1 = this.factory.createObject(MsWordApp.class); this.ob2 = this.factory.createObject(MsWordApp.class); diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacksFactory_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacksFactory_Test.java new file mode 100644 index 0000000000..ea2038c2cf --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacksFactory_Test.java @@ -0,0 +1,292 @@ +/* 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.util; + +import com.sun.jna.platform.win32.COM.COMUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +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.Guid.IID; +import com.sun.jna.platform.win32.Guid.REFIID; +import com.sun.jna.platform.win32.OaIdl; +import com.sun.jna.platform.win32.Variant; +import com.sun.jna.platform.win32.Variant.VARIANT; +import com.sun.jna.platform.win32.WinNT.HRESULT; +import com.sun.jna.ptr.PointerByReference; +import org.hamcrest.CoreMatchers; + +import static com.sun.jna.platform.win32.COM.IUnknown.IID_IUNKNOWN; +import static com.sun.jna.platform.win32.COM.IDispatch.IID_IDISPATCH; +import static org.junit.Assert.*; + +public class ComEventCallbacksFactory_Test { + + static { + ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); + } + + Factory factory; + + @Before + public void before() { + ComThread thread = new ComThread("Default Factory COM Thread", 10000, new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + //ignore + } + }); + this.factory = new Factory(thread); + } + + @After + public void after() { + this.factory.disposeAll(); + this.factory.getComThread().terminate(10000); + } + + + @ComObject(progId="Internet.Explorer.1", clsId = "{0002DF01-0000-0000-C000-000000000046}") + interface ComInternetExplorer extends ComIWebBrowser2 { + } + + @ComInterface(iid="{D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}") + interface ComIWebBrowser2 extends IUnknown, IConnectionPoint { + @ComProperty + boolean getVisible(); + + @ComProperty + void setVisible(boolean value); + + @ComMethod + void Quit(); + + @ComMethod + /** + * 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=DWebBrowserEvents2.IID) + interface DWebBrowserEvents2 { + public static final String IID = "{34A715A0-6587-11D0-924A-0020AFC7AC4D}"; + + @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 DWebBrowserEvents2_Listener extends AbstractComEventCallbackListener implements DWebBrowserEvents2 { + + @Override + public void errorReceivingCallbackEvent(String message, Exception exception) { +// System.err.println(message); +// if(exception != null) { +// System.err.println(exception.getMessage()); +// exception.printStackTrace(System.err); +// } + } + + 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) { + // 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 OnQuit() { + Quit_called = true; + } + } + + @Test + 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); + + iWebBrowser2.Quit(); + + //Wait for event to happen + Thread.sleep(200); + + Assert.assertNotNull(listener.Quit_called); + Assert.assertTrue(listener.Quit_called); + } + + @Test + 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; + + iWebBrowser2.Quit(); + + Thread.sleep(200); + + Assert.assertNotNull(listener.Quit_called); + Assert.assertFalse(listener.Quit_called); + } + + @Test + 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 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); + } + + @Test + public void testComEventCallback() { + DWebBrowserEvents2_Listener listener = new DWebBrowserEvents2_Listener(); + CallbackProxy proxy = new CallbackProxy(factory, DWebBrowserEvents2.class, listener); + + REFIID refiid = new REFIID(new IID(DWebBrowserEvents2.IID)); + + // precondition: the structures for the listenedToRiid and + // refiid have to be different (else the PointerType#equals would + // be enough + assertFalse(proxy.listenedToRiid.getPointer().equals(refiid.getPointer())); + + // Neverthe less, the QueryInterface method has to return the + // correct pointer (the IID is relevant, not its wrapper + PointerByReference interfacePointer = new PointerByReference(); + + // Check the "business" interface + HRESULT hr = proxy.QueryInterface(refiid, interfacePointer); + assertTrue(COMUtils.SUCCEEDED(hr)); + assertEquals(interfacePointer.getValue(), proxy.getPointer()); + + // IUnknown must be implemented + hr = proxy.QueryInterface(new REFIID(IID_IUNKNOWN), interfacePointer); + assertTrue(COMUtils.SUCCEEDED(hr)); + assertEquals(interfacePointer.getValue(), proxy.getPointer()); + + // Currently only Dispatch based callbacks are supported, + // so this interface must be present to + hr = proxy.QueryInterface(new REFIID(IID_IDISPATCH), interfacePointer); + assertTrue(COMUtils.SUCCEEDED(hr)); + assertEquals(interfacePointer.getValue(), proxy.getPointer()); + + // Negative check -- this has to fail, the IID should not be + // assigned + hr = proxy.QueryInterface(new REFIID(new IID("{00000000-0000-0000-C000-000000000000}")), interfacePointer); + assertTrue(COMUtils.FAILED(hr)); + } +} 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/ComEventCallbacksObjectFactory_Test.java similarity index 96% rename from contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacks_Test.java rename to contrib/platform/test/com/sun/jna/platform/win32/COM/util/ComEventCallbacksObjectFactory_Test.java index 3488753e34..c06f94e089 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/ComEventCallbacksObjectFactory_Test.java @@ -38,18 +38,18 @@ import static com.sun.jna.platform.win32.COM.IDispatch.IID_IDISPATCH; import static org.junit.Assert.*; -public class ComEventCallbacks_Test { +public class ComEventCallbacksObjectFactory_Test { static { ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); } - Factory factory; + ObjectFactory factory; @Before public void before() { Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED); - this.factory = new Factory(); + this.factory = new ObjectFactory(); } @After diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ConvertTest.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ConvertTest.java index c76fc5a741..bc1971bff6 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ConvertTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ConvertTest.java @@ -26,12 +26,12 @@ // Untested: Proxy public class ConvertTest { - private static Factory fact; + private static ObjectFactory fact; @BeforeClass public static void init() { Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED); - fact = new Factory(); + fact = new ObjectFactory(); } @AfterClass 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 index b375a91930..f55a0b69c8 100644 --- 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 @@ -72,7 +72,7 @@ public void setUp() throws Exception { @Test public void testOfficeInvocationProblemCOMUtil() { - Factory fact = new Factory(); + ObjectFactory fact = new ObjectFactory(); Application app; try { app = fact.createObject(Application.class); diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/IDispatchTest.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/IDispatchTest.java index 24254fa24d..e202d7b639 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/IDispatchTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/IDispatchTest.java @@ -16,12 +16,12 @@ public class IDispatchTest { - Factory factory; + ObjectFactory factory; @Before public void before() { Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED); - this.factory = new Factory(); + this.factory = new ObjectFactory(); } @After diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java new file mode 100644 index 0000000000..5ac09cfce5 --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectFactory_Test.java @@ -0,0 +1,188 @@ +/* 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.util; + +import com.sun.jna.Pointer; +import static org.junit.Assert.*; + +import java.io.File; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.sun.jna.platform.win32.COM.util.annotation.ComInterface; +import com.sun.jna.platform.win32.COM.util.annotation.ComObject; +import com.sun.jna.platform.win32.COM.util.annotation.ComMethod; +import com.sun.jna.platform.win32.COM.util.annotation.ComProperty; +import com.sun.jna.platform.win32.Ole32; + +public class ProxyObjectFactory_Test { + + static { + ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); + } + + @ComInterface(iid="{00020970-0000-0000-C000-000000000046}") + interface Application extends IUnknown { + @ComProperty + boolean getVisible(); + + @ComProperty + void setVisible(boolean value); + + @ComMethod + void Quit(boolean SaveChanges, Object OriginalFormat, Boolean RouteDocument); + + @ComMethod + public void Quit(Object... someArgs); + + @ComMethod(dispId = 0x00000183) + public float PointsToPixels(float points, Object... someArgs); + + @ComProperty(dispId = 0x00000006) + public Documents getDocuments(); + } + + @ComInterface(iid = "{0002096C-0000-0000-C000-000000000046}") + public interface Documents extends IDispatch { + @ComMethod + public _Document Add(Object template, Object newTemplate, Object documentType, Object visible); + + @ComMethod + public _Document Add(Object... someArgs); + } + + @ComInterface(iid = "{0002096B-0000-0000-C000-000000000046}") + public interface _Document extends IDispatch { + @ComMethod + public void SaveAs(Object fileName, Object fileFormat, Object lockComments, Object password, + Object addToRecentFiles, Object writePassword, Object readOnlyRecommended, Object embedTrueTypeFonts, + Object saveNativePictureFormat, Object saveFormsData, Object saveAsAOCELetter, Object encoding, + Object insertLineBreaks, Object allowSubstitutions, Object lineEnding, Object addBiDiMarks); + + @ComMethod + public void SaveAs(Object... someArgs); + } + + public enum WdSaveFormat implements IComEnum { + wdFormatDocument(0), wdFormatText(2), wdFormatRTF(6), wdFormatHTML(8), wdFormatPDF(17); + + private long _value; + + private WdSaveFormat(long value) { + _value = value; + } + + @Override + public long getValue() { + return _value; + } + } + + @ComObject(progId="Word.Application") + interface MsWordApp extends Application { + } + + Factory factory; + + @Before + public void before() { + this.factory = new Factory(); + //ensure there are no word applications running. + while(true) { + try { + MsWordApp ao = this.factory.fetchObject(MsWordApp.class); + Application a = ao.queryInterface(Application.class); + try { + a.Quit(true, null, null); + try { + //wait for it to quit + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace();e.getCause().printStackTrace(); + } + } catch(Exception e) { + break; + } + } + } + + @After + public void after() { + factory.disposeAll(); + factory.getComThread().terminate(10000); + } + + + @Test + public void equals() { + MsWordApp comObj1 = this.factory.createObject(MsWordApp.class); + MsWordApp comObj2 = this.factory.fetchObject(MsWordApp.class); + + boolean res = comObj1.equals(comObj2); + + assertTrue(res); + + comObj1.Quit(false, null,null); + } + + @Test + public void notEquals() { + MsWordApp comObj1 = this.factory.createObject(MsWordApp.class); + MsWordApp comObj2 = this.factory.createObject(MsWordApp.class); + + boolean res = comObj1.equals(comObj2); + + assertFalse(res); + + comObj1.Quit(false, null,null); + } + + @Test + public void accessWhilstDisposing() { + MsWordApp comObj1 = this.factory.createObject(MsWordApp.class); + + //TODO: how to test this? + + this.factory.disposeAll(); + + } + + @Test + public void testVarargsCallWithoutVarargParameter() { + MsWordApp comObj = this.factory.createObject(MsWordApp.class); + + // call must work without exception: + float f = comObj.PointsToPixels(25.3f); + comObj.Quit(); + } + + @Test + public void testVarargsCallWithParameter() { + MsWordApp comObj = this.factory.createObject(MsWordApp.class); + + Documents documents = comObj.getDocuments(); + _Document myDocument = documents.Add(); + + String path = new File(".").getAbsolutePath(); + myDocument.SaveAs(path + "\\abcdefg", WdSaveFormat.wdFormatPDF); + comObj.Quit(); + + boolean wasDeleted = new File("abcdefg.pdf").delete(); + assertTrue(wasDeleted); + } +} diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObject_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java similarity index 94% rename from contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObject_Test.java rename to contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java index 51bd2f3145..4b6ccee62f 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObject_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java @@ -27,7 +27,7 @@ import com.sun.jna.platform.win32.COM.util.annotation.ComProperty; import com.sun.jna.platform.win32.Ole32; -public class ProxyObject_Test { +public class ProxyObjectObjectFactory_Test { static { ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); @@ -94,12 +94,12 @@ public long getValue() { interface MsWordApp extends Application { } - Factory factory; + ObjectFactory factory; @Before public void before() { Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED); - this.factory = new Factory(); + this.factory = new ObjectFactory(); //ensure there are no word applications running. while(true) { try { 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 5ad76959e4..c95784d77a 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 @@ -50,13 +50,13 @@ interface Application extends IUnknown { interface MsWordApp extends Application { } - Factory factory; + ObjectFactory factory; MsWordApp msWord; @Before public void before() { Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED); - this.factory = new Factory(); + this.factory = new ObjectFactory(); //ensure there is only one word application running. while(true) { try { diff --git a/contrib/platform/test/com/sun/jna/platform/win32/SAFEARRAYTest.java b/contrib/platform/test/com/sun/jna/platform/win32/SAFEARRAYTest.java index 426a1d1aac..2af404f303 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/SAFEARRAYTest.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/SAFEARRAYTest.java @@ -15,7 +15,7 @@ import com.sun.jna.Pointer; import com.sun.jna.platform.win32.COM.COMException; import com.sun.jna.platform.win32.COM.COMUtils; -import com.sun.jna.platform.win32.COM.util.Factory; +import com.sun.jna.platform.win32.COM.util.ObjectFactory; import com.sun.jna.platform.win32.COM.util.IComEnum; import com.sun.jna.platform.win32.COM.util.IConnectionPoint; import com.sun.jna.platform.win32.COM.util.IUnknown; @@ -59,6 +59,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import org.junit.Before; +import static com.sun.jna.platform.win32.OaIdlUtil.toPrimitiveArray; public class SAFEARRAYTest { static { @@ -112,7 +113,7 @@ public void testSafeArrayPutGetElement() throws Exception { @Ignore("Only for live testing") @Test public void testPerformance() { - Factory fact = new Factory(); + ObjectFactory fact = new ObjectFactory(); // Open a record set with a sample search (basicly get the first five // entries from the search index @@ -446,7 +447,7 @@ public void testDataTypes() { */ @Test public void testADODB() { - Factory fact = new Factory(); + ObjectFactory fact = new ObjectFactory(); // Open a record set with a sample search (basicly get the first five // entries from the search index