From 4e2d45e3ff7923db43093b32f27b2f88bfa570cf Mon Sep 17 00:00:00 2001 From: clay_shooter Date: Fri, 28 Oct 2005 22:58:51 +0000 Subject: [PATCH] Patch 1208570 added parameters that let a user provide typelib information required to get Excel events --- docs/ReleaseNotes.html | 19 ++-- jni/DispatchEvents.cpp | 126 +++++++++++++++------ jni/DispatchEvents.h | 17 +-- src/com/jacob/com/DispatchEvents.java | 99 ++++++++-------- src/com/jacob/com/InvocationProxy.java | 12 +- unittest/com/jacob/com/ExcelEventTest.java | 80 +++++++++---- unittest/com/jacob/com/WordEventTest.java | 75 ++++++++++++ 7 files changed, 295 insertions(+), 133 deletions(-) create mode 100644 unittest/com/jacob/com/WordEventTest.java diff --git a/docs/ReleaseNotes.html b/docs/ReleaseNotes.html index d7325b7..1057648 100644 --- a/docs/ReleaseNotes.html +++ b/docs/ReleaseNotes.html @@ -11,15 +11,14 @@
  • - Nothing Yet + Event Callbacks -
  • -
  • - Nothing Yet -
  • @@ -41,8 +40,8 @@ Patches - 00000 - none at this time + 1208570 + Support Excel and other objects events   diff --git a/jni/DispatchEvents.cpp b/jni/DispatchEvents.cpp index d90b93c..54ad882 100644 --- a/jni/DispatchEvents.cpp +++ b/jni/DispatchEvents.cpp @@ -36,7 +36,9 @@ extern "C" // defined below BOOL GetEventIID(IUnknown*, IID*, CComBSTR **, DISPID **, int *,LPOLESTR); +BOOL GetEventIIDForTypeLib(BSTR, IID*, CComBSTR **, DISPID **, int *,LPOLESTR); BOOL getClassInfoFromProgId(LPOLESTR bsProgId,LPTYPEINFO *pClassInfo); +BOOL MapEventIIDs(IID*, CComBSTR **, DISPID **, int *, LPOLESTR , LPTYPEINFO ); // extract a EventProxy* from a jobject EventProxy *extractProxy(JNIEnv *env, jobject arg) @@ -56,25 +58,42 @@ void putProxy(JNIEnv *env, jobject arg, EventProxy *ep) env->SetIntField(arg, ajf, (jint)ep); } + /* - * Class: DispatchEvents - * Method: init2 - * Signature: (LDispatch;Ljava/lang/Object;Ljava/lang/String;)V + * Class: com_jacob_com_DispatchEvents + * Method: init3 + * Signature: (Lcom/jacob/com/Dispatch;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V */ -JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init2 - (JNIEnv *env, - jobject _this, jobject src, - jobject sink, - jstring _progid) +JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init3 + (JNIEnv *env, + jobject _this, jobject src, + jobject sink, + jstring _progid, + jstring _typelib) { USES_CONVERSION; - // find progid if any - LPOLESTR bsProgId = NULL; - if (_progid!=NULL) { - const char *progid = env->GetStringUTFChars(_progid, NULL); - bsProgId = A2W(progid); + + if (_typelib != NULL && _progid == NULL){ + // both are required if typelib exists + ThrowComFail(env,"TypeLib was specified but no program id was",-1); + return; + } + + BSTR typeLib = NULL; + if (_typelib != NULL){ + const char *typelib = env->GetStringUTFChars(_typelib, NULL); + typeLib = A2W(typelib); + //printf("we have a type lib %ls\n",typeLib); } + // find progid if any + LPOLESTR bsProgId = NULL; + if (_progid!=NULL) { + const char *progid = env->GetStringUTFChars(_progid, NULL); + bsProgId = A2W(progid); + //printf("we have an applicaton %ls\n",bsProgId); + } + // get the IDispatch for the source object IDispatch *pDisp = extractDispatch(env, src); CComQIPtr pUnk(pDisp); @@ -86,19 +105,26 @@ JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init2 ThrowComFail(env, "Can't find IConnectionPointContainer", -1); return; } - // hook up to the default source iid - CComPtr pCP; + IID eventIID; CComBSTR *mNames; DISPID *mIDs; int n_EventMethods; - if (!GetEventIID(pUnk, &eventIID, &mNames, &mIDs, &n_EventMethods,bsProgId)) { - ThrowComFail(env, "Can't find event iid", -1); - return; + if (_typelib == NULL){ + if (!GetEventIID(pUnk, &eventIID, &mNames, &mIDs, &n_EventMethods,bsProgId)) { + ThrowComFail(env, "Can't find event iid", -1); + return; + } + } else { + if (!GetEventIIDForTypeLib(typeLib, &eventIID, &mNames, &mIDs, &n_EventMethods,bsProgId)) { + ThrowComFail(env, "Can't find event iid for type lib", -1); + return; + } } + + // hook up to the default source iid + CComPtr pCP; HRESULT hr = pCPC->FindConnectionPoint(eventIID, &pCP); - // VC++ 6.0 compiler realiized we weren't using this variable - //DWORD dwEventCookie; if (SUCCEEDED(hr)) { EventProxy *ep = new EventProxy(env, sink, pCP, eventIID, mNames, mIDs, n_EventMethods); @@ -109,17 +135,6 @@ JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init2 } } -/* - * Class: DispatchEvents - * Method: init - * Signature: (LDispatch;Ljava/lang/Object;)V - */ -JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init - (JNIEnv *env, jobject _this, jobject src, jobject sink) -{ - Java_com_jacob_com_DispatchEvents_init2(env,_this,src,sink, NULL); -} - /* * Class: DispatchEvents * Method: release @@ -179,7 +194,6 @@ LoadNameCache(LPTYPEINFO pTypeInfo, LPTYPEATTR pta, #define IMPLTYPE_DEFAULTSOURCE \ (IMPLTYPEFLAG_FDEFAULT | IMPLTYPEFLAG_FSOURCE) - BOOL GetEventIID(IUnknown *m_pObject, IID* piid, CComBSTR **mNames, DISPID **mIDs, int *nmeth,LPOLESTR bsProgId) { @@ -200,17 +214,22 @@ BOOL GetEventIID(IUnknown *m_pObject, IID* piid, else if (getClassInfoFromProgId(bsProgId,&pClassInfo)) { } else { - printf("couldn't get IProvideClassInfo\n"); + printf("GetEventIID: couldn't get IProvideClassInfo\n"); return false; } + return MapEventIIDs(piid, mNames, mIDs, nmeth, bsProgId, pClassInfo); +} - //printf("got ClassInfo\n"); +BOOL MapEventIIDs(IID* piid, + CComBSTR **mNames, DISPID **mIDs, int *nmeth, LPOLESTR bsProgId, LPTYPEINFO pClassInfo) +{ ATLASSERT(pClassInfo != NULL); + //printf("MapEventIIDs: got past ClassInfo assert\n"); LPTYPEATTR pClassAttr; if (SUCCEEDED(pClassInfo->GetTypeAttr(&pClassAttr))) { - //printf("got TypeAttr\n"); + //printf("MapEventIIDs: got TypeAttr\n"); ATLASSERT(pClassAttr != NULL); ATLASSERT(pClassAttr->typekind == TKIND_COCLASS); @@ -218,6 +237,7 @@ BOOL GetEventIID(IUnknown *m_pObject, IID* piid, int nFlags; HREFTYPE hRefType; + //printf("MapEventIIDs: looking at %d class attribute impl types \n"); for (unsigned int i = 0; i < pClassAttr->cImplTypes; i++) { if (SUCCEEDED(pClassInfo->GetImplTypeFlags(i, &nFlags)) && @@ -300,6 +320,42 @@ BOOL getClassInfoFromProgId(LPOLESTR bsProgId,LPTYPEINFO *pClassInfo) return true; } +/* + * Get the class info from the progId using the given typeLib. + */ +BOOL getClassInfoFromProgIdTypeLib(BSTR typeLib, LPOLESTR bsProgId, LPTYPEINFO *pClassInfo) +{ + USES_CONVERSION; + CLSID clsid; + + if (FAILED(CLSIDFromProgID(bsProgId, &clsid))) return false; + if (FAILED(StringFromCLSID(clsid,&bsProgId))) return false; + + ITypeLib* pITypeLib; + if (FAILED(LoadTypeLib(typeLib, &pITypeLib))) return false; + + //Find ITypeInfo for coclass. + pITypeLib->GetTypeInfoOfGuid(clsid, pClassInfo); + pITypeLib->Release(); + return true; } +BOOL GetEventIIDForTypeLib(BSTR typeLib, IID* piid, + CComBSTR **mNames, DISPID **mIDs, int *nmeth,LPOLESTR bsProgId) +{ + LPTYPEINFO pClassInfo = NULL; + if(getClassInfoFromProgIdTypeLib(typeLib, bsProgId,&pClassInfo)) + { + if (pClassInfo == NULL){ + printf("we had a successful return but pClassInfo is null\n"); + } + return MapEventIIDs(piid, mNames, mIDs, nmeth, bsProgId, pClassInfo); + } + else + { + printf("GetEventIIDForTypeLib: couldn't get IProvideClassInfo\n"); + return false; + } +} +} diff --git a/jni/DispatchEvents.h b/jni/DispatchEvents.h index e0c21c9..ce02ea5 100644 --- a/jni/DispatchEvents.h +++ b/jni/DispatchEvents.h @@ -25,21 +25,14 @@ #ifdef __cplusplus extern "C" { #endif -/* - * Class: DispatchEvents - * Method: init - * Signature: (LDispatch;Ljava/lang/Object;)V - */ -JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init - (JNIEnv *, jobject, jobject, jobject); /* - * Class: DispatchEvents - * Method: init2 - * Signature: (LDispatch;Ljava/lang/Object;Ljava/lang/String;)V + * Class: com_jacob_com_DispatchEvents + * Method: init3 + * Signature: (Lcom/jacob/com/Dispatch;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V */ -JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init2 - (JNIEnv *, jobject, jobject, jobject, jstring); +JNIEXPORT void JNICALL Java_com_jacob_com_DispatchEvents_init3 + (JNIEnv *, jobject, jobject, jobject, jstring, jstring); /* * Class: DispatchEvents diff --git a/src/com/jacob/com/DispatchEvents.java b/src/com/jacob/com/DispatchEvents.java index ff12697..d578e9e 100644 --- a/src/com/jacob/com/DispatchEvents.java +++ b/src/com/jacob/com/DispatchEvents.java @@ -30,6 +30,14 @@ package com.jacob.com; * Variant... as a parameter. It will then wrap the call back data * in the Variant array and call the java method of the object * that this DispatchEvents object was initialized with. + *

    + * Instances of this class are created with "sink object" that + * will receive the event messages. The sink object is wrapped in + * an Invocation handler that actually receives the messages and then + * forwards them on to the "sink object". The constructors recognize when + * an instance of InvocationProxy is passed in and do not create + * a new InvocationProxy as a wrapper. They instead use the passed in + * InvocationProxy. * */ public class DispatchEvents extends JacobObject { @@ -50,30 +58,8 @@ public class DispatchEvents extends JacobObject { /** - * A constructor for those that want to supply their own InvocationProxy. - * or subclass (future implementations may take an interface). - * This lets someone distribute their events to other objects - * at the single method dispatch point. + * This is the most commonly used constructor *

    - * Most users of this class will use the other constructors! - * - * @param sourceOfEvent the Dispatch object that will send events - * @param pInvocationProxy the proxy object that expects to receive the - * events for some other object - */ - public DispatchEvents(Dispatch sourceOfEvent, InvocationProxy pInvocationProxy){ - mInvocationProxy = pInvocationProxy; - if (mInvocationProxy != null){ - init(sourceOfEvent, mInvocationProxy); - } else { - if (JacobObject.isDebugEnabled()){ - JacobObject.debug("Cannot register null invocation proxy for events"); - } - throw new IllegalArgumentException("Cannot register null invocation proxy for events"); - } - } - - /** * Creates the event callback linkage between the the * MS program represented by the Dispatch object and the * Java object that will receive the callback. @@ -81,22 +67,12 @@ public class DispatchEvents extends JacobObject { * @param eventSink Java object that wants to receive the events */ public DispatchEvents(Dispatch sourceOfEvent, Object eventSink) { - if (JacobObject.isDebugEnabled()){ - System.out.println( - "DispatchEvents: Registering "+ eventSink + "for events "); - } - mInvocationProxy = new InvocationProxy(eventSink); - if (mInvocationProxy != null){ - init(sourceOfEvent, mInvocationProxy); - } else { - if (JacobObject.isDebugEnabled()){ - JacobObject.debug("Cannot register null event sink for events"); - } - throw new IllegalArgumentException("Cannot register null event sink for events"); - } + this(sourceOfEvent, eventSink, null ); } /** + * None of the samples use this constructor + *

    * Creates the event callback linkage between the the * MS program represented by the Dispatch object and the * Java object that will receive the callback. @@ -105,13 +81,37 @@ public class DispatchEvents extends JacobObject { * @param progId ??? */ public DispatchEvents(Dispatch sourceOfEvent, Object eventSink, String progId) { + this(sourceOfEvent, eventSink, progId, null ); + } + + /** + * Creates the event callback linkage between the the + * MS program represented by the Dispatch object and the + * Java object that will receive the callback. + *

    +     * >DispatchEvents de = 
    +     * 			new DispatchEvents(someDispatch,someEventHAndler,
    +     * 				"Excel.Application",
    +     * 				"C:\\Program Files\\Microsoft Office\\OFFICE11\\EXCEL.EXE");
    +     *
    +     * @param sourceOfEvent Dispatch object who's MS app will generate callbacks
    +     * @param eventSink Java object that wants to receive the events
    +     * @param progId , mandatory if the typelib is specified
    +     * @param typeLib The location of the typelib to use
    +     */
    +    public DispatchEvents(Dispatch sourceOfEvent, Object eventSink, String progId, String typeLib)
    +    {
     		if (JacobObject.isDebugEnabled()){
     			System.out.println(
     					"DispatchEvents: Registering "+ eventSink + "for events ");
     		}
    -    	mInvocationProxy = new InvocationProxy(eventSink);
    +		if (eventSink instanceof InvocationProxy) {
    +			mInvocationProxy = (InvocationProxy) eventSink;
    +		} else {
    +			mInvocationProxy = new InvocationProxy(eventSink);
    +		}
         	if (mInvocationProxy != null) {
    -	        init2(sourceOfEvent, mInvocationProxy, progId);
    +	        init3(sourceOfEvent, mInvocationProxy, progId, typeLib);
     		} else {
     			if (JacobObject.isDebugEnabled()){
     				JacobObject.debug("Cannot register null event sink for events");
    @@ -124,20 +124,15 @@ public class DispatchEvents extends JacobObject {
          * hooks up a connection point proxy by progId
          * event methods on the sink object will be called
          * by name with a signature of (Variant[] args)
    -     * @param src
    -     * @param sink
    +     *
    +     * You must specify the location of the typeLib.
    +     * 
    +     * @param src dispatch that is the source of the messages
    +     * @param sink the object that will receive the messages
    +     * @param progId optional program id.  most folks don't need this either
    +     * @param typeLib optional parameter for those programs that don't register their type libs (like Excel)
          */
    -    protected native void init(Dispatch src, Object sink);
    -
    -    /**
    -     * hooks up a connection point proxy by progId
    -     * event methods on the sink object will be called
    -     * by name with a signature of (Variant[] args)
    -     * @param src
    -     * @param sink
    -     * @param progId
    -     */
    -    protected native void init2(Dispatch src, Object sink, String progId);
    +    protected native void init3(Dispatch src, Object sink, String progId, String typeLib);
     
         /**
          *  now private so only this object can asccess
    @@ -176,4 +171,4 @@ public class DispatchEvents extends JacobObject {
         static {
             System.loadLibrary("jacob");
         }
    -}
    \ No newline at end of file
    +}
    diff --git a/src/com/jacob/com/InvocationProxy.java b/src/com/jacob/com/InvocationProxy.java
    index 53d9095..0ffc4a8 100644
    --- a/src/com/jacob/com/InvocationProxy.java
    +++ b/src/com/jacob/com/InvocationProxy.java
    @@ -44,7 +44,17 @@ public class InvocationProxy {
     	Object mTargetObject = null;
     	
     	/**
    -	 * constructs an invocation proxy that fronts for an event listener 
    +	 * dummy constructor for subclasses that don't actually wrap
    +	 * anything and just want to override the invoke() method 
    +	 */
    +	protected InvocationProxy(){
    +		
    +	}
    +	
    +	/**
    +	 * Constructs an invocation proxy that fronts for an event listener.
    +	 * The InvocationProxy will wrap the target object and forward
    +	 * any received messages to the wrapped object 
     	 * @param pTargetObject
     	 */
     	protected InvocationProxy(Object pTargetObject){
    diff --git a/unittest/com/jacob/com/ExcelEventTest.java b/unittest/com/jacob/com/ExcelEventTest.java
    index b3bf9c4..bc75ceb 100644
    --- a/unittest/com/jacob/com/ExcelEventTest.java
    +++ b/unittest/com/jacob/com/ExcelEventTest.java
    @@ -6,45 +6,79 @@ import com.jacob.com.DispatchEvents;
     
     /**
      * This test was lifted from a forum posting and shows how you can't listen to
    - * Excel events added post 1.9.1 Eclipse Settings...
    + * Excel events (added post 1.9.1 Eclipse Settings.)  This also uses the 1.9.1 
    + * InvocationProxy to receive the events.
    + * 

    supported command line options with default values are * -Djava.library.path=d:/jacob/release -Dcom.jacob.autogc=false - * -Dcom.jacob.debug=true + * -Dcom.jacob.debug=false */ -public class ExcelEventTest { +public class ExcelEventTest extends InvocationProxy { + /** + * load up excel, register for events and make stuff happen + * @param args + */ public static void main(String args[]) { - - listenTo("Word.Application",null); - - // Create an Excel Listener - listenTo("Excel.Application", - "C:\\Program Files\\Microsoft Office\\OFFICE11\\EXCEL.EXE"); - } - - private static void listenTo(String pid, String typeLibLocation) { + String pid = "Excel.Application"; + String typeLibLocation = "C:\\Program Files\\Microsoft Office\\OFFICE11\\EXCEL.EXE"; // Grab The Component. ActiveXComponent axc = new ActiveXComponent(pid); try { // Add a listener (doesn't matter what it is). DispatchEvents de; - if (typeLibLocation == null){ + if (typeLibLocation == null) { de = new DispatchEvents(axc, new ExcelEventTest()); } else { - de = new DispatchEvents(axc, new ExcelEventTest(), - pid,typeLibLocation); + de = new DispatchEvents(axc, new ExcelEventTest(), pid, + typeLibLocation); } - if (de == null){ - System.out.println( - "No exception thrown but now dispatch returned for Excel events"); + if (de == null) { + System.out + .println("No exception thrown but no dispatch returned for Excel events"); + } else { + // Yea! + System.out.println("Successfully attached to " + pid); + } - // Yea! - System.out.println("Successfully attached to " + pid); - } catch (ComFailException e) { - e.printStackTrace(); + + System.out.println("version=" + axc.getProperty("Version")); + System.out.println("version=" + Dispatch.get(axc, "Version")); + axc.setProperty("Visible", true); + Dispatch workbooks = axc.getPropertyAsComponent("Workbooks"); + Dispatch workbook = Dispatch.get(workbooks, "Add").toDispatch(); + Dispatch sheet = Dispatch.get(workbook, "ActiveSheet").toDispatch(); + Dispatch a1 = Dispatch.invoke(sheet, "Range", Dispatch.Get, + new Object[] { "A1" }, new int[1]).toDispatch(); + Dispatch a2 = Dispatch.invoke(sheet, "Range", Dispatch.Get, + new Object[] { "A2" }, new int[1]).toDispatch(); + Dispatch.put(a1, "Value", "123.456"); + Dispatch.put(a2, "Formula", "=A1*2"); + System.out.println("Retrieved a1 from excel:" + Dispatch.get(a1, "Value")); + System.out.println("REtrieved a2 from excel:" + Dispatch.get(a2, "Value")); + Variant f = new Variant(false); + Dispatch.call(workbook, "Close", f); + axc.invoke("Quit", new Variant[] {}); + + } catch (ComFailException cfe) { + cfe.printStackTrace(); System.out.println("Failed to attach to " + pid + ": " - + e.getMessage()); + + cfe.getMessage()); } } + + /** + * dummy consturctor to create an InvocationProxy that wraps nothing + */ + public ExcelEventTest() { + } + + /** + * override the invoke method to loga ll the events + */ + public void invoke(String methodName, Variant targetParameter[]) { + System.out.println("Received event from Windows program" + methodName); + } + } diff --git a/unittest/com/jacob/com/WordEventTest.java b/unittest/com/jacob/com/WordEventTest.java new file mode 100644 index 0000000..119d845 --- /dev/null +++ b/unittest/com/jacob/com/WordEventTest.java @@ -0,0 +1,75 @@ +package com.jacob.com; + +import com.jacob.activeX.ActiveXComponent; +import com.jacob.com.ComFailException; +import com.jacob.com.DispatchEvents; + +/** + * This test was lifted from a forum posting and shows how you can't listen to + * Excel events (added post 1.9.1 Eclipse Settings.) This also uses the 1.9.1 + * InvocationProxy to receive the events. + *

    supported command line options with default values are + * -Djava.library.path=d:/jacob/release -Dcom.jacob.autogc=false + * -Dcom.jacob.debug=false + */ +public class WordEventTest extends InvocationProxy { + + /** + * load up excel, register for events and make stuff happen + * @param args + */ + public static void main(String args[]) { + String pid = "Word.Application"; + String typeLibLocation = null; + + // Grab The Component. + ActiveXComponent axc = new ActiveXComponent(pid); + try { + // Add a listener (doesn't matter what it is). + DispatchEvents de; + if (typeLibLocation == null) { + de = new DispatchEvents(axc, new WordEventTest()); + } else { + de = new DispatchEvents(axc, new WordEventTest(), pid, + typeLibLocation); + } + if (de == null) { + System.out + .println("No exception thrown but no dispatch returned for Word events"); + } else { + // Yea! + System.out.println("Successfully attached to " + pid); + + } + // this is different from the ExcelEventTest because it uses + // the jacob active X api instead of the Dispatch api + System.out.println("version=" + axc.getPropertyAsString("Version")); + axc.setProperty("Visible",true); + ActiveXComponent documents = axc.getPropertyAsComponent("Documents"); + if (documents == null){ + System.out.println("unable to get documents"); + } + axc.invoke("Quit", new Variant[] {}); + + } catch (ComFailException cfe) { + cfe.printStackTrace(); + System.out.println("Failed to attach to " + pid + ": " + + cfe.getMessage()); + + } + } + + /** + * dummy consturctor to create an InvocationProxy that wraps nothing + */ + public WordEventTest() { + } + + /** + * override the invoke method to loga ll the events + */ + public void invoke(String methodName, Variant targetParameter[]) { + System.out.println("Received event from Windows program" + methodName); + } + +}