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