From 22ec6990ce46beebb3e422233027dc9ae6d5b916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Mon, 1 Jan 2018 17:32:16 +0100 Subject: [PATCH 1/4] Rework COMException to also carry the HRESULT This allows filtering exception based on HRESULT value. For example GetActiveObject returns a HRESULT of 0x800401E3 (MK_E_UNAVAILABLE) if the target object is not active. For many cases this is not an error. A valid construct would be: public void getExistingDemoObjectOrCreate() { DemoObject obj; try { return factory.fetchObject(DemoObject.class); } catch (COMException ex) { if(ex.matchesHresult(WinError.MK_E_UNAVAILABLE)) { return factory.createObject(DemoObject.class); } else { throw ex; } } } --- .../jna/platform/win32/COM/COMException.java | 98 ++++++++++++------- .../sun/jna/platform/win32/COM/COMUtils.java | 8 +- .../jna/platform/win32/COM/util/Factory.java | 6 +- .../win32/COM/util/IConnectionPoint.java | 2 +- .../win32/COM/util/ObjectFactory.java | 2 +- .../platform/win32/COM/util/ProxyObject.java | 48 ++++++--- .../COM/util/ProxyObjectFactory_Test.java | 97 +++++++++++------- .../util/ProxyObjectObjectFactory_Test.java | 77 ++++++++++----- 8 files changed, 227 insertions(+), 111 deletions(-) diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java index ad2d734972..05edafea6f 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java @@ -24,9 +24,8 @@ package com.sun.jna.platform.win32.COM; import com.sun.jna.platform.win32.OaIdl.EXCEPINFO; -import com.sun.jna.ptr.IntByReference; +import com.sun.jna.platform.win32.WinNT.HRESULT; -// TODO: Auto-generated Javadoc /** * Exception class for all COM related classes. * @@ -35,19 +34,17 @@ public class COMException extends RuntimeException { private static final long serialVersionUID = 1L; - /** The p excep info. */ - private EXCEPINFO pExcepInfo; + private final EXCEPINFO pExcepInfo; - /** The pu arg err. */ - private IntByReference puArgErr; + private final Integer errorArg; - private int uArgErr; + private final HRESULT hresult; /** * Instantiates a new automation exception. */ public COMException() { - super(); + this("", (Throwable) null); } /** @@ -55,11 +52,20 @@ public COMException() { * * @param message * the message + */ + public COMException(String message) { + this(message, (Throwable) null); + } + + + /** + * Instantiates a new automation exception. + * * @param cause * the cause */ - public COMException(String message, Throwable cause) { - super(message, cause); + public COMException(Throwable cause) { + this(null, cause); } /** @@ -67,9 +73,14 @@ public COMException(String message, Throwable cause) { * * @param message * the message + * @param cause + * the cause */ - public COMException(String message) { - super(message); + public COMException(String message, Throwable cause) { + super(message, cause); + this.errorArg = null; + this.hresult = null; + this.pExcepInfo = null; } /** @@ -77,32 +88,35 @@ public COMException(String message) { * * @param message * the message - * @param pExcepInfo - * the excep info - * @param puArgErr - * the pu arg err + * @param hresult + * HRESULT that lead to the creation of the COMException */ - public COMException(String message, EXCEPINFO pExcepInfo, - IntByReference puArgErr) { - super(message + " (puArgErr=" + (null==puArgErr?"":puArgErr.getValue()) + ")"); - this.pExcepInfo = pExcepInfo; - this.puArgErr = puArgErr; + public COMException(String message, HRESULT hresult) { + this(message, null, null, hresult); } /** * Instantiates a new automation exception. * - * @param cause - * the cause + * @param message + * the message + * @param pExcepInfo + * the excep info + * @param argErr + * the errorArg + * @param hresult + * HRESULT that lead to the creation of the COMException */ - public COMException(Throwable cause) { - super(cause); + public COMException(String message, EXCEPINFO pExcepInfo, + Integer argErr, HRESULT hresult) { + super(formatMessage(message, argErr)); + this.pExcepInfo = pExcepInfo; + this.errorArg = argErr; + this.hresult = hresult; } /** - * Gets the excep info. - * - * @return the excep info + * @return retrieve EXCEPINFO if present */ public EXCEPINFO getExcepInfo() { return pExcepInfo; @@ -113,15 +127,31 @@ public EXCEPINFO getExcepInfo() { * * @return the arg err */ - public IntByReference getArgErr() { - return puArgErr; + public Integer getErrorArg() { + return errorArg; } - public int getuArgErr() { - return uArgErr; + /** + * @return the HRESULT that lead to thie COMException or NULL if the COMException as not directly caused by a native call + */ + public HRESULT getHresult() { + return hresult; + } + + /** + * @param errorCode + * @return true if the exception has an associated HRESULT and that HRESULT + * matches the supplied error code + */ + public boolean matchesErrorCode(int errorCode) { + return hresult != null && hresult.intValue() == errorCode; } - public void setuArgErr(int uArgErr) { - this.uArgErr = uArgErr; + private static String formatMessage(String message, Integer errArg) { + if(errArg != null) { + return message + " (puArgErr=" + errArg + ")"; + } else { + return message; + } } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java index a5df8235f9..3252b791d3 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java @@ -36,6 +36,7 @@ import com.sun.jna.platform.win32.OaIdl.EXCEPINFO; import com.sun.jna.platform.win32.Ole32; import com.sun.jna.platform.win32.W32Errors; +import com.sun.jna.platform.win32.WinError; import com.sun.jna.platform.win32.WinNT; import com.sun.jna.platform.win32.WinNT.HRESULT; import com.sun.jna.platform.win32.WinReg; @@ -128,7 +129,12 @@ public static void checkRC(HRESULT hr, EXCEPINFO pExcepInfo, // throws if HRESULT can't be resolved formatMessage = "(HRESULT: " + Integer.toHexString(hr.intValue()) + ")"; } - throw new COMException(formatMessage, pExcepInfo, puArgErr); + if(hr.intValue() == WinError.DISP_E_TYPEMISMATCH || + hr.intValue() == WinError.DISP_E_PARAMNOTFOUND) { + throw new COMException(formatMessage, pExcepInfo, puArgErr.getValue(), hr); + } else { + throw new COMException(formatMessage, pExcepInfo, null, hr); + } } } 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 f98ce57e36..583b709c7a 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 @@ -24,6 +24,7 @@ package com.sun.jna.platform.win32.COM.util; +import com.sun.jna.platform.win32.COM.COMException; 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; @@ -136,7 +137,7 @@ public Guid.GUID call() throws Exception { } @Override - public T fetchObject(final Class comInterface) { + public T fetchObject(final Class comInterface) throws COMException { // Proxy2 is added by createProxy inside fetch Object return runInComThread(new Callable() { public T call() throws Exception { @@ -173,6 +174,9 @@ private T runInComThread(Callable callable) { } catch (InterruptedException ex) { throw new RuntimeException(ex); } catch (ExecutionException ex) { + if(ex.getCause() instanceof RuntimeException) { + throw (RuntimeException) ex.getCause(); + } throw new RuntimeException(ex); } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/IConnectionPoint.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/IConnectionPoint.java index 93bd7635eb..2595f2a484 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/util/IConnectionPoint.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/util/IConnectionPoint.java @@ -46,5 +46,5 @@ public interface IConnectionPoint { * @param comEventCallbackInterface - the interface that is being listened to * @param cookie - the cookie that was returned when advise was called */ - void unadvise(Class comEventCallbackInterface, final IComEventCallbackCookie cookie); + void unadvise(Class comEventCallbackInterface, final IComEventCallbackCookie cookie) throws COMException; } 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 index 301afca0aa..dd86772bf9 100644 --- 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 @@ -126,7 +126,7 @@ public T createObject(Class comInterface) { * Gets and existing COM object (GetActiveObject) for the given progId and * returns a ProxyObject for the given interface. */ - public T fetchObject(Class comInterface) { + public T fetchObject(Class comInterface) throws COMException { assert COMUtils.comIsInitialized() : "COM not initialized"; ComObject comObectAnnotation = comInterface.getAnnotation(ComObject.class); 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 9ffd41fcb3..60ac006706 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 @@ -117,11 +117,16 @@ private long getUnknownId() { int n = dispatch.Release(); } else { String formatMessageFromHR = Kernel32Util.formatMessage(hr); - throw new COMException("getUnknownId: " + formatMessageFromHR); + throw new COMException("getUnknownId: " + formatMessageFromHR, hr); } - } catch (Exception e) { + } catch (RuntimeException e) { + // Do not rewrap COMException + if(e instanceof COMException) { + throw e; + } else { throw new COMException("Error occured when trying get Unknown Id ", e); - } + } + } } return this.unknownId; } @@ -253,7 +258,7 @@ public Object invoke(final Object proxy, final java.lang.reflect.Method method, } // ---------------------- IConnectionPoint ---------------------- - private ConnectionPoint fetchRawConnectionPoint(IID iid) throws InterruptedException, ExecutionException, TimeoutException { + private ConnectionPoint fetchRawConnectionPoint(IID iid) { assert COMUtils.comIsInitialized() : "COM not initialized"; // query for ConnectionPointContainer @@ -271,7 +276,8 @@ private ConnectionPoint fetchRawConnectionPoint(IID iid) throws InterruptedExcep } public IComEventCallbackCookie advise(Class comEventCallbackInterface, - final IComEventCallbackListener comEventCallbackListener) { + final IComEventCallbackListener comEventCallbackListener) + throws COMException { assert COMUtils.comIsInitialized() : "COM not initialized"; try { @@ -300,13 +306,17 @@ public IComEventCallbackCookie advise(Class comEventCallbackInterface, // return the cookie so that a call to stop listening can be made return new ComEventCallbackCookie(pdwCookie.getValue()); - } catch (Exception e) { - throw new COMException("Error occured in advise when trying to connect the listener " - + comEventCallbackListener, e); + } catch (RuntimeException e) { + // Do not rewrap COMException + if(e instanceof COMException) { + throw e; + } else { + throw new COMException("Error occured in advise when trying to connect the listener " + comEventCallbackListener, e); + } } } - public void unadvise(Class comEventCallbackInterface, final IComEventCallbackCookie cookie) { + public void unadvise(Class comEventCallbackInterface, final IComEventCallbackCookie cookie) throws COMException { assert COMUtils.comIsInitialized() : "COM not initialized"; try { @@ -324,8 +334,13 @@ public void unadvise(Class comEventCallbackInterface, final IComEventCallback rawCp.Release(); COMUtils.checkRC(hr); - } catch (Exception e) { - throw new COMException("Error occured in unadvise when trying to disconnect the listener from " + this, e); + } catch (RuntimeException e) { + // Do not rewrap COMException + if(e instanceof COMException) { + throw e; + } else { + throw new COMException("Error occured in unadvise when trying to disconnect the listener from " + this, e); + } } } @@ -448,10 +463,15 @@ public T queryInterface(Class comInterface) throws COMException { return t; } else { String formatMessageFromHR = Kernel32Util.formatMessage(hr); - throw new COMException("queryInterface: " + formatMessageFromHR); + throw new COMException("queryInterface: " + formatMessageFromHR, hr); } - } catch (Exception e) { - throw new COMException("Error occured when trying to query for interface " + comInterface.getName(), e); + } catch (RuntimeException e) { + // Do not rewrap COMException + if(e instanceof COMException) { + throw e; + } else { + throw new COMException("Error occured when trying to query for interface " + comInterface.getName(), e); + } } } 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 index 65dc70925a..edc3fc3a25 100644 --- 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 @@ -12,9 +12,12 @@ */ package com.sun.jna.platform.win32.COM.util; +import com.sun.jna.platform.win32.COM.COMException; import static org.junit.Assert.*; import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; import org.junit.After; import org.junit.Before; @@ -24,21 +27,23 @@ 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.WinError; public class ProxyObjectFactory_Test { + private static final Logger LOG = Logger.getLogger(ProxyObjectFactory_Test.class.getName()); 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); @@ -50,7 +55,7 @@ interface Application extends IUnknown { @ComProperty(dispId = 0x00000006) public Documents getDocuments(); - } + } @ComInterface(iid = "{0002096C-0000-0000-C000-000000000046}") public interface Documents extends IDispatch { @@ -91,75 +96,99 @@ public long getValue() { @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; + 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) { + LOG.log(Level.INFO, null, e); + } + } catch (COMException e) { + LOG.log(Level.INFO, null, e); + LOG.log(Level.INFO, null, e.getCause()); + } + } catch(COMException e) { + if(e.getHresult() != null) { + if(e.matchesErrorCode(WinError.MK_E_UNAVAILABLE)) { + break; + } else if (e.matchesErrorCode(WinError.RPC_E_DISCONNECTED)) { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + } + } + } else { + throw e; + } } } } - + @After public void after() { factory.disposeAll(); factory.getComThread().terminate(10000); } - - + + @Test + public void testFetchNotExistingObject() { + COMException exceptionRaised = null; + try { + MsWordApp comObj2 = this.factory.fetchObject(MsWordApp.class); + } catch (COMException ex) { + exceptionRaised = ex; + } + assertNotNull("fetchObject on a non-running Object must raise an exception", exceptionRaised); + assertEquals("Unexpected error code", exceptionRaised.getHresult().intValue(), WinError.MK_E_UNAVAILABLE); + assertTrue("Error code not matched", exceptionRaised.matchesErrorCode(WinError.MK_E_UNAVAILABLE)); + } + @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); diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java index 36f4ef6bb3..8cbd5e553a 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java @@ -13,10 +13,12 @@ package com.sun.jna.platform.win32.COM.util; import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.COM.COMException; import static org.junit.Assert.*; import java.lang.reflect.Proxy; - +import java.util.logging.Level; +import java.util.logging.Logger; import java.io.File; import org.junit.After; @@ -28,21 +30,23 @@ 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; +import com.sun.jna.platform.win32.WinError; public class ProxyObjectObjectFactory_Test { + private static final Logger LOG = Logger.getLogger(ProxyObjectObjectFactory_Test.class.getName()); 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); @@ -54,7 +58,7 @@ interface Application extends IUnknown { @ComProperty(dispId = 0x00000006) public Documents getDocuments(); - } + } @ComInterface(iid = "{0002096C-0000-0000-C000-000000000046}") public interface Documents extends IDispatch { @@ -95,9 +99,9 @@ public long getValue() { @ComObject(progId="Word.Application") interface MsWordApp extends Application { } - + ObjectFactory factory; - + @Before public void before() { Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED); @@ -113,59 +117,83 @@ public void before() { //wait for it to quit Thread.sleep(100); } catch (InterruptedException e) { - e.printStackTrace(); + LOG.log(Level.INFO, null, e); } - } catch (Exception e) { - e.printStackTrace();e.getCause().printStackTrace(); + } catch (COMException e) { + LOG.log(Level.INFO, null, e); + LOG.log(Level.INFO, null, e.getCause()); } - } catch(Exception e) { - break; + } catch(COMException e) { + if(e.getHresult() != null) { + if(e.matchesErrorCode(WinError.MK_E_UNAVAILABLE)) { + break; + } else if (e.matchesErrorCode(WinError.RPC_E_DISCONNECTED)) { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + } + } + } else { + throw e; + } } } } - + @After public void after() { factory.disposeAll(); Ole32.INSTANCE.CoUninitialize(); } - - + + @Test + public void testFetchNotExistingObject() { + COMException exceptionRaised = null; + try { + MsWordApp comObj2 = this.factory.fetchObject(MsWordApp.class); + } catch (COMException ex) { + exceptionRaised = ex; + } + assertNotNull("fetchObject on a non-running Object must raise an exception", exceptionRaised); + assertEquals("Unexpected error code", exceptionRaised.getHresult().intValue(), WinError.MK_E_UNAVAILABLE); + assertTrue("Error code not matched", exceptionRaised.matchesErrorCode(WinError.MK_E_UNAVAILABLE)); + } + @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); comObj1.Quit(); //TODO: how to test this? - + this.factory.disposeAll(); - + } - + @Test public void testVarargsCallWithoutVarargParameter() { MsWordApp comObj = this.factory.createObject(MsWordApp.class); @@ -190,7 +218,6 @@ public void testVarargsCallWithParameter() { assertTrue(wasDeleted); } - @Test public void testDisposeMustBeCallableMultipleTimes() { MsWordApp comObj = this.factory.createObject(MsWordApp.class); From 92bbd927472c46dfc2f512ba203439da496d5c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sat, 6 Jan 2018 21:18:01 +0100 Subject: [PATCH 2/4] Fix netbeans project definition --- contrib/platform/nbproject/project.properties | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/contrib/platform/nbproject/project.properties b/contrib/platform/nbproject/project.properties index 2a65ed977c..2f595eea00 100644 --- a/contrib/platform/nbproject/project.properties +++ b/contrib/platform/nbproject/project.properties @@ -1,40 +1,63 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true application.args= +application.title=jna-platform +application.vendor=JNA project build.classes.dir=${build.dir}/classes build.classes.excludes=**/*.java # This directory is removed when the project is cleaned: build.dir=build build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources # Only compile against the classpath explicitly listed here: build.sysclasspath=ignore build.test.classes.dir=${build.dir}/test/classes build.test.results.dir=${build.dir}/test/results debug.classpath=\ ${run.classpath} +debug.modulepath=\ + ${run.modulepath} debug.test.classpath=\ ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} # This directory is removed when the project is cleaned: dist.dir=dist dist.jar=${dist.dir}/jna-platform.jar dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +file.reference.hamcrest-core-1.3.jar=../../lib/hamcrest-core-1.3.jar file.reference.jna.jar=../../build/jna.jar file.reference.jna-test.jar=../../build/jna-test.jar -libs.junit.classpath=\ - ../../lib/junit.jar:\ - ../../lib/hamcrest-core-1.3.jar +file.reference.junit.jar=../../lib/junit.jar +includes=** +javac.external.vm=false +javac.modulepath= +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.test.modulepath=\ + ${javac.modulepath} +javadoc.html5=false +jlink.launcher=false +jlink.launcher.name=platform jar.compress=false javac.classpath=\ - ${file.reference.jna.jar}:\ - ${file.reference.jna.build}/test-classes + ${file.reference.jna.jar} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false -javac.source=1.5 -javac.target=1.5 +javac.source=1.6 +javac.target=1.6 javac.test.classpath=\ ${javac.classpath}:\ ${file.reference.jna-test.jar}:\ ${build.classes.dir}:\ - ${libs.junit.classpath} + ${file.reference.junit.jar}:\ + ${file.reference.hamcrest-core-1.3.jar} javadoc.additionalparam= javadoc.author=false javadoc.encoding= @@ -49,6 +72,7 @@ javadoc.windowtitle= main.class= manifest.file=manifest.mf meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false platform.active=default_platform run.classpath=\ ${javac.classpath}:\ @@ -57,6 +81,8 @@ run.classpath=\ # (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value # or test-sys-prop.name=value to set system properties for unit tests): run.jvmargs= +run.modulepath=\ + ${javac.modulepath} run.test.classpath=\ ${javac.test.classpath}:\ ../../lib/test/reflections-0.9.8.jar:\ @@ -65,7 +91,9 @@ run.test.classpath=\ ../../lib/test/slf4j-api-1.6.1.jar:\ ../../lib/test/dom4j-1.6.1.jar:\ ${build.test.classes.dir} +run.test.modulepath=\ + ${javac.test.modulepath} src.dir=src test.src.dir=test source.encoding=UTF-8 -file.encoding=UTF-8 \ No newline at end of file +file.encoding=UTF-8 From bb408ff612c09050bbfc7b104429becc3368db2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sat, 6 Jan 2018 22:28:01 +0100 Subject: [PATCH 3/4] Introduce COMInvokeException and prevent a memory leak in case of failure Only a subset of errors contain exception data. Only calls to IDispatch#Invoke generate exception details and potentially a "fault index" for parameters. Therefore these are separated into an extended COMInvokeException. Additionally the EXCEPINFO data was not freeed, leading to memory leaks in in repeated calls. Add unittest for COMExceptions and correct Exception extraction in Factory --- .../win32/COM/COMBindingBaseObject.java | 2 +- .../jna/platform/win32/COM/COMException.java | 52 +---- .../win32/COM/COMInvokeException.java | 191 ++++++++++++++++++ .../sun/jna/platform/win32/COM/COMUtils.java | 99 +++++++-- .../jna/platform/win32/COM/util/Factory.java | 23 ++- .../COM/util/ProxyObjectFactory_Test.java | 21 ++ .../util/ProxyObjectObjectFactory_Test.java | 21 ++ 7 files changed, 337 insertions(+), 72 deletions(-) create mode 100644 contrib/platform/src/com/sun/jna/platform/win32/COM/COMInvokeException.java diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java index 7463a79eaa..e8dc4fcb03 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMBindingBaseObject.java @@ -336,6 +336,6 @@ protected HRESULT oleMethod(int nType, VARIANT.ByReference pvResult, * the hr */ protected void checkFailed(HRESULT hr) { - COMUtils.checkRC(hr, null, null); + COMUtils.checkRC(hr); } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java index 05edafea6f..de20267808 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMException.java @@ -23,7 +23,6 @@ */ package com.sun.jna.platform.win32.COM; -import com.sun.jna.platform.win32.OaIdl.EXCEPINFO; import com.sun.jna.platform.win32.WinNT.HRESULT; /** @@ -34,10 +33,6 @@ public class COMException extends RuntimeException { private static final long serialVersionUID = 1L; - private final EXCEPINFO pExcepInfo; - - private final Integer errorArg; - private final HRESULT hresult; /** @@ -78,9 +73,7 @@ public COMException(Throwable cause) { */ public COMException(String message, Throwable cause) { super(message, cause); - this.errorArg = null; this.hresult = null; - this.pExcepInfo = null; } /** @@ -92,45 +85,10 @@ public COMException(String message, Throwable cause) { * HRESULT that lead to the creation of the COMException */ public COMException(String message, HRESULT hresult) { - this(message, null, null, hresult); - } - - /** - * Instantiates a new automation exception. - * - * @param message - * the message - * @param pExcepInfo - * the excep info - * @param argErr - * the errorArg - * @param hresult - * HRESULT that lead to the creation of the COMException - */ - public COMException(String message, EXCEPINFO pExcepInfo, - Integer argErr, HRESULT hresult) { - super(formatMessage(message, argErr)); - this.pExcepInfo = pExcepInfo; - this.errorArg = argErr; + super(message); this.hresult = hresult; } - /** - * @return retrieve EXCEPINFO if present - */ - public EXCEPINFO getExcepInfo() { - return pExcepInfo; - } - - /** - * Gets the arg err. - * - * @return the arg err - */ - public Integer getErrorArg() { - return errorArg; - } - /** * @return the HRESULT that lead to thie COMException or NULL if the COMException as not directly caused by a native call */ @@ -146,12 +104,4 @@ public HRESULT getHresult() { public boolean matchesErrorCode(int errorCode) { return hresult != null && hresult.intValue() == errorCode; } - - private static String formatMessage(String message, Integer errArg) { - if(errArg != null) { - return message + " (puArgErr=" + errArg + ")"; - } else { - return message; - } - } } diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMInvokeException.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMInvokeException.java new file mode 100644 index 0000000000..d0e63835e1 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMInvokeException.java @@ -0,0 +1,191 @@ +/* Copyright (c) 2017 Matthias Bläsing, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32.COM; + +import com.sun.jna.platform.win32.WinError; +import com.sun.jna.platform.win32.WinNT.HRESULT; + +/** + * Exception class for error origination from an COM invoke + */ +public class COMInvokeException extends COMException { + private static final long serialVersionUID = 1L; + + private final Integer wCode; + private final String source; + private final String description; + private final String helpFile; + private final Integer helpContext; + private final Integer scode; + private final Integer errorArg; + + /** + * Instantiates a new automation exception. + */ + public COMInvokeException() { + this("", (Throwable) null); + } + + /** + * Instantiates a new automation exception. + * + * @param message + * the message + */ + public COMInvokeException(String message) { + this(message, (Throwable) null); + } + + + /** + * Instantiates a new automation exception. + * + * @param cause + * the cause + */ + public COMInvokeException(Throwable cause) { + this(null, cause); + } + + /** + * Instantiates a new automation exception. + * + * @param message + * the message + * @param cause + * the cause + */ + public COMInvokeException(String message, Throwable cause) { + super(message, cause); + this.description = null; + this.errorArg = null; + this.helpContext = null; + this.helpFile = null; + this.scode = null; + this.source = null; + this.wCode = null; + } + + /** + * Instantiates a new automation exception. + * + * @param message exception message + * @param hresult hresult of the invoke call + * @param errorArg the position of the argument that caused the error + * @param description The exception description to display. If no + * description is available, use null. + * @param helpContext The help context ID. + * @param helpFile The fully qualified help file path. If no Help is + * available, use null. + * @param scode A return value that describes the error. Either this field + * or wCode (but not both) must be filled in; the other must be set to 0. + * (16-bit Windows versions only.) + * @param source The name of the exception source. Typically, this is an + * application name. This field should be filled in by the implementor of + * IDispatch. + * @param wCode The error code. Error codes should be greater than 1000. + * Either this field or the scode field must be filled in; the other must be + * set to 0. + */ + public COMInvokeException(String message, HRESULT hresult, Integer errorArg, + String description, Integer helpContext, String helpFile, + Integer scode, String source, Integer wCode) { + super(formatMessage(hresult, message, errorArg), hresult); + this.description = description; + this.errorArg = errorArg; + this.helpContext = helpContext; + this.helpFile = helpFile; + this.scode = scode; + this.source = source; + this.wCode = wCode; + } + + /** + * Gets the arg err. + * + * @return the arg err + */ + public Integer getErrorArg() { + return errorArg; + } + + /** + * @return The error code. Error codes should be greater than 1000. Either + * this field or the scode field must be filled in; the other must be set to + * 0. It is NULL if no exception info was created + */ + public Integer getWCode() { + return wCode; + } + + /** + * @return The name of the exception source. Typically, this is an + * application name. This field should be filled in by the implementor of + * IDispatch. NULL if no exception info was created + */ + public String getSource() { + return source; + } + + /** + * @return The exception description to display. If no description is + * available, use null. + */ + public String getDescription() { + return description; + } + + /** + * @return The fully qualified help file path. If no Help is available, use + * null. + */ + public String getHelpFile() { + return helpFile; + } + + /** + * @return The help context ID or NULL if not present + */ + public Integer getHelpContext() { + return helpContext; + } + + /** + * @return A return value that describes the error. Either this field or + * wCode (but not both) must be filled in; the other must be set to 0. + * (16-bit Windows versions only.) NULL if no exception info was created. + */ + public Integer getScode() { + return scode; + } + + private static String formatMessage(HRESULT hresult, String message, Integer errArg) { + if (hresult.intValue() == WinError.DISP_E_TYPEMISMATCH + || hresult.intValue() == WinError.DISP_E_PARAMNOTFOUND) { + return message + " (puArgErr=" + errArg + ")"; + } else { + return message; + } + } +} diff --git a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java index 3252b791d3..e3bae281c1 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/COM/COMUtils.java @@ -35,8 +35,8 @@ import com.sun.jna.platform.win32.Kernel32Util; import com.sun.jna.platform.win32.OaIdl.EXCEPINFO; import com.sun.jna.platform.win32.Ole32; +import com.sun.jna.platform.win32.OleAuto; import com.sun.jna.platform.win32.W32Errors; -import com.sun.jna.platform.win32.WinError; import com.sun.jna.platform.win32.WinNT; import com.sun.jna.platform.win32.WinNT.HRESULT; import com.sun.jna.platform.win32.WinReg; @@ -106,36 +106,111 @@ public static boolean FAILED(int hr) { * the hr */ public static void checkRC(HRESULT hr) { - checkRC(hr, null, null); + if (FAILED(hr)) { + String formatMessage; + try { + formatMessage = Kernel32Util.formatMessage(hr) + "(HRESULT: " + Integer.toHexString(hr.intValue()) + ")"; + } catch (LastErrorException ex) { + // throws if HRESULT can't be resolved + formatMessage = "(HRESULT: " + Integer.toHexString(hr.intValue()) + ")"; + } + throw new COMException(formatMessage, hr); + } } /** - * Throw new exception. + * Check status of HRESULT if it indicates a failed call a COMInvokeException + * is reaised. + * + *

The string members of the pExcepInfo are freed in this call and can't + * be used afterwards. The structure is not freeed, as it is expected, that + * is allocated via the Memory object of JNA.

* * @param hr * the hr * @param pExcepInfo - * the excep info + * the excep info, it is expected * @param puArgErr * the pu arg err */ public static void checkRC(HRESULT hr, EXCEPINFO pExcepInfo, IntByReference puArgErr) { + + COMException resultException = null; + if (FAILED(hr)) { - String formatMessage; + StringBuilder formatMessage = new StringBuilder(); + + Integer errorArg = null; + Integer wCode = null; + Integer scode = null; + String description = null; + String helpFile = null; + Integer helpCtx = null; + String source = null; + + if(puArgErr != null) { + errorArg = puArgErr.getValue(); + } + try { - formatMessage = Kernel32Util.formatMessage(hr) + "(HRESULT: " + Integer.toHexString(hr.intValue()) + ")"; + formatMessage.append(Kernel32Util.formatMessage(hr)); } catch (LastErrorException ex) { // throws if HRESULT can't be resolved - formatMessage = "(HRESULT: " + Integer.toHexString(hr.intValue()) + ")"; } - if(hr.intValue() == WinError.DISP_E_TYPEMISMATCH || - hr.intValue() == WinError.DISP_E_PARAMNOTFOUND) { - throw new COMException(formatMessage, pExcepInfo, puArgErr.getValue(), hr); - } else { - throw new COMException(formatMessage, pExcepInfo, null, hr); + + formatMessage.append("(HRESULT: "); + formatMessage.append(Integer.toHexString(hr.intValue())); + formatMessage.append(")"); + + if(pExcepInfo != null) { + wCode = pExcepInfo.wCode.intValue(); + scode = pExcepInfo.scode.intValue(); + helpCtx = pExcepInfo.dwHelpContext.intValue(); + + if(pExcepInfo.bstrSource != null) { + source = pExcepInfo.bstrSource.getValue(); + formatMessage.append("\nSource: "); + formatMessage.append(source); + } + if(pExcepInfo.bstrDescription != null) { + description = pExcepInfo.bstrDescription.getValue(); + formatMessage.append("\nDescription: "); + formatMessage.append(description); + } + if(pExcepInfo.bstrHelpFile != null) { + helpFile = pExcepInfo.bstrHelpFile.getValue(); + } + } + + throw new COMInvokeException( + formatMessage.toString(), + hr, + errorArg, + description, + helpCtx, + helpFile, + scode, + source, + wCode + ); + } + + if(pExcepInfo != null) { + if(pExcepInfo.bstrSource != null) { + OleAuto.INSTANCE.SysFreeString(pExcepInfo.bstrSource); + } + if(pExcepInfo.bstrDescription != null) { + OleAuto.INSTANCE.SysFreeString(pExcepInfo.bstrDescription); + } + if(pExcepInfo.bstrHelpFile != null) { + OleAuto.INSTANCE.SysFreeString(pExcepInfo.bstrHelpFile); } } + + if(resultException != null) { + throw resultException; + } } /** 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 583b709c7a..f65da63663 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 @@ -36,6 +36,7 @@ import com.sun.jna.platform.win32.WinNT; import com.sun.jna.ptr.IntByReference; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.Callable; @@ -91,12 +92,12 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg } } - return comThread.execute(new Callable() { - @Override - public Object call() throws Exception { - return method.invoke(delegate, args); - } - }); + return runInComThread(new Callable() { + @Override + public Object call() throws Exception { + return method.invoke(delegate, args); + } + }); } } @@ -174,8 +175,14 @@ private T runInComThread(Callable callable) { } catch (InterruptedException ex) { throw new RuntimeException(ex); } catch (ExecutionException ex) { - if(ex.getCause() instanceof RuntimeException) { - throw (RuntimeException) ex.getCause(); + Throwable cause = ex.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof InvocationTargetException) { + cause = ((InvocationTargetException) cause).getTargetException(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } } throw new RuntimeException(ex); } 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 index edc3fc3a25..4c66f4d5a1 100644 --- 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 @@ -13,6 +13,7 @@ package com.sun.jna.platform.win32.COM.util; import com.sun.jna.platform.win32.COM.COMException; +import com.sun.jna.platform.win32.COM.COMInvokeException; import static org.junit.Assert.*; import java.io.File; @@ -212,4 +213,24 @@ public void testVarargsCallWithParameter() { boolean wasDeleted = new File("abcdefg.pdf").delete(); assertTrue(wasDeleted); } + + @Test + public void testVarargsCallWithInvalidParameter() { + MsWordApp comObj = this.factory.createObject(MsWordApp.class); + + Documents documents = comObj.getDocuments(); + + COMInvokeException invokeException = null; + + try { + documents.Add("Not_existing_template"); + } catch (COMInvokeException ex) { + invokeException = ex; + } + + assertNotNull(invokeException); + assertEquals("Wrong hresult", WinError.DISP_E_EXCEPTION, invokeException.getHresult().intValue()); + assertTrue("hresult was not matched", invokeException.matchesErrorCode(WinError.DISP_E_EXCEPTION)); + assertEquals("Wrong scode", (long) 0x800a1436, (long) invokeException.getScode()); + } } diff --git a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java index 8cbd5e553a..3bb51065ff 100644 --- a/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java +++ b/contrib/platform/test/com/sun/jna/platform/win32/COM/util/ProxyObjectObjectFactory_Test.java @@ -14,6 +14,7 @@ import com.sun.jna.Pointer; import com.sun.jna.platform.win32.COM.COMException; +import com.sun.jna.platform.win32.COM.COMInvokeException; import static org.junit.Assert.*; import java.lang.reflect.Proxy; @@ -225,4 +226,24 @@ public void testDisposeMustBeCallableMultipleTimes() { ((ProxyObject) Proxy.getInvocationHandler(comObj)).dispose(); ((ProxyObject) Proxy.getInvocationHandler(comObj)).dispose(); } + + @Test + public void testVarargsCallWithInvalidParameter() { + MsWordApp comObj = this.factory.createObject(MsWordApp.class); + + Documents documents = comObj.getDocuments(); + + COMInvokeException invokeException = null; + + try { + documents.Add("Not_existing_template"); + } catch (COMInvokeException ex) { + invokeException = ex; + } + + assertNotNull(invokeException); + assertEquals("Wrong hresult", WinError.DISP_E_EXCEPTION, invokeException.getHresult().intValue()); + assertTrue("hresult was not matched", invokeException.matchesErrorCode(WinError.DISP_E_EXCEPTION)); + assertEquals("Wrong scode", (long) 0x800a1436, (long) invokeException.getScode()); + } } From bf680a549fd8bf59112657cd9533b5b2bb0f9d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sun, 7 Jan 2018 11:14:37 +0100 Subject: [PATCH 4/4] Add COMException changes to CHANGES.md and adjust msoffice demo to follow the changes --- CHANGES.md | 4 ++++ .../sun/jna/platform/win32/COM/office/MSOfficeDemo.java | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7be58366a2..2ed375efe2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Release 5.0.0 (Next release) Features -------- +* [#903](https://github.com/java-native-access/jna/pull/903): Carry `HRESULT` in `c.s.j.p.win32.COM.COMException`, introduce `c.s.j.p.win32.COM.COMInvokeException` as subclass of `COMException` for exception as the result of a `IDispatch#Invoke`. The `EXECPINFO` is unwrapped into fields in the `COMInvokeException` and correctly freed. - [@matthiasblaesing](https://github.com/matthiasblaesing). * [#822](https://github.com/java-native-access/jna/issues/822): `Native#loadLibrary` requires that the interface class passed in is an instance of Library. The runtime check can be enhanced by using a constraint generic. This breaks binary compatibility (see notes below) - [@d-noll](https://github.com/d-noll). * [#889](https://github.com/java-native-access/jna/issues/889): The `Structure#newInstance` receive the target type as a parameter. This adds a limited generic type, so that the return type ist the target type and not a generic structure, removing the necessity to do an explizit cast - [@matthiasblaesing](https://github.com/matthiasblaesing). @@ -69,6 +70,9 @@ Breaking Changes * `com.sun.jna.platform.win32.Kernel32Util.formatMessageFromHR(HRESULT)` was replaced by `com.sun.jna.platform.win32.Kernel32Util.formatMessage(HRESULT)` +* `com.sun.jna.platform.win32.COM.COMException` was structurally modified. The + `pExcepInfo` and `puArgErr` members were removed and `hresult` member was added. + The now missing information in `COMException` was moved to `COMInvokeException`. Release 4.5.0 ============= diff --git a/contrib/msoffice/src/com/sun/jna/platform/win32/COM/office/MSOfficeDemo.java b/contrib/msoffice/src/com/sun/jna/platform/win32/COM/office/MSOfficeDemo.java index 3230182654..372aa0df40 100644 --- a/contrib/msoffice/src/com/sun/jna/platform/win32/COM/office/MSOfficeDemo.java +++ b/contrib/msoffice/src/com/sun/jna/platform/win32/COM/office/MSOfficeDemo.java @@ -26,6 +26,7 @@ import com.sun.jna.Pointer; import com.sun.jna.platform.win32.COM.COMException; +import com.sun.jna.platform.win32.COM.COMInvokeException; import com.sun.jna.platform.win32.COM.Helper; import com.sun.jna.platform.win32.Ole32; import com.sun.jna.platform.win32.WinDef.LONG; @@ -102,11 +103,10 @@ public void testMSWord() throws IOException { msWord.insertText("Hello some changes from JNA!\n"); // save the document and prompt the user msWord.Save(false, wdPromptUser); + } catch (COMInvokeException e) { + System.out.println("bstrSource: " + e.getSource()); + System.out.println("bstrDescription: " + e.getDescription()); } catch (COMException e) { - if (e.getExcepInfo() != null) { - System.out.println("bstrSource: " + e.getExcepInfo().bstrSource); - System.out.println("bstrDescription: " + e.getExcepInfo().bstrDescription); - } } finally { if (msWord != null) { msWord.quit();