From 4dc7dc18c58e62f455cab88d1dcad08e222ba1d1 Mon Sep 17 00:00:00 2001 From: clay_shooter <> Date: Wed, 18 Apr 2007 02:59:39 +0000 Subject: [PATCH] SF 1702604 - alpha prototype for activeXInvocationProxy event callbacks --- jacob/docs/EventCallbacks.htm | 408 ------------------ jacob/docs/EventCallbacks.html | 87 ++++ jacob/docs/ReleaseNotes.html | 7 + .../jacob/activeX/ActiveXDispatchEvents.java | 95 ++++ .../jacob/activeX/ActiveXInvocationProxy.java | 167 +++++++ jacob/src/com/jacob/com/DispatchEvents.java | 15 +- jacob/src/com/jacob/com/InvocationProxy.java | 147 ++----- .../jacob/com/InvocationProxyAllVariants.java | 114 +++++ jacob/src/com/jacob/com/Variant.java | 5 +- .../com/jacob/test/events/ExcelEventTest.java | 5 +- .../com/jacob/test/events/IETest.java | 20 +- .../jacob/test/events/IETestActiveXProxy.java | 207 +++++++++ .../com/jacob/test/events/WordEventTest.java | 4 +- 13 files changed, 747 insertions(+), 534 deletions(-) delete mode 100644 jacob/docs/EventCallbacks.htm create mode 100644 jacob/docs/EventCallbacks.html create mode 100644 jacob/src/com/jacob/activeX/ActiveXDispatchEvents.java create mode 100644 jacob/src/com/jacob/activeX/ActiveXInvocationProxy.java create mode 100644 jacob/src/com/jacob/com/InvocationProxyAllVariants.java create mode 100644 jacob/unittest/com/jacob/test/events/IETestActiveXProxy.java diff --git a/jacob/docs/EventCallbacks.htm b/jacob/docs/EventCallbacks.htm deleted file mode 100644 index 1d4a23a..0000000 --- a/jacob/docs/EventCallbacks.htm +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - -Jacob can register Java classes for MS application events or callbacks - - - - - - - -
- -

Jacob can register Java classes for MS application events -or callbacks.  The normal flow for this -is:

- -

 

- -

1)        -Application thread creates an instance of the -event handler and registers it with Jacob

- -

2)        -The application continues on doing other work.

- -

3)        -Some time later, the MS application takes some -action and initiates the event callback.

- -

4)        -The Java VM receives the event and spins up a -new thread to handle it.

- -

5)        -The Jacob jni EventProxy in the dll is called by -the VM.

- -

6)        -The Jacob jni EventProxy creates Variant objects -to handle the parameters of the passed in event.

- -

7)        -The Jacob jni EventProxy sends the name of the -callback and the array of Variant objects to the Java  InvocationProxy that was registered to catch -events.

- -

8)        -The Java InvocationProxy uses reflection to map -the event name to a method name with the exact same name.

- -

9)        -The Java InvocationProxy sends the message to -the registered event handler and returns if the event handler is of type void -(standard behavior). 

- -

10)    The -Java InvocationProxy sends the message to the registered event handler and -returns the Variant that resulted from the call back to the Jacob jni -EventProxy that then returns it to the windows calling program.

- -

 

- -

Swing developers should note that this message comes in -on a thread other than the event thread.  -All objects receiving events that require user intervention or drawing -in the UI should use invokeLater() to post requests for actions onto the event -queue.  Failure to do so will insure random -failures in the GUI.

- -

 

- -

 

- -

Java Web Start (JWS) and other launchers can have -additional issues related to the class loader.  -The Jacob C++ library uses FindClass() to find the Variant class when -building the parameter list.  FindClass() -uses the system class loader which includes only the classes specified at run -time or in the CLASSPATH.  Most of the -application classes in this situation live in an alternate set of class loaders -that were created when the launcher located and ran the application -classes.  This means that the search for -Variant will fail usually with the silent and immediate termination of the Java -application.  The thread classloader -probably can’t be used to try and find the class because this new thread does -not have a classloader associated with it other than the system class -loader.  The end result is that the -Variant class needs to be located via other means and that the thread -classloader should be set to be the context class loader of the event handler -class.

- -

 

- -

<b>1.8 and 1.9 behavior</b>

- -

The Jacob EventProxy class has been modified (off of the -1.8 tree) so that it takes a two step approach  towards fixing these problems.

- -

           

- -

1)        -The EventProxy constructor now accepts an extra -object, an instance of the Variant class.  -This gives the EventProxy a way to get to the Variant class and thus to -its classloader. All of the callers of the constructor have been modified to -pass a Variant object to the EventProxy without programmer intervention.

- -

2)        -EventProxy first attempts to locate the Variant -class using FindClass()

- -

3)        -Failing that, it looks to see if a variant -object had been passed in. If so, it calls class() and goes from there. 

- -

4)        -If all that fails, it logs a message and then -fails in the spectacular fashion of the previous versions.

- -

<b>1.10 behavior</b>

- -

The Jacob EventProxy class has been modified so that it -takes a different approach towards fixing this problem.

- -

1.    All -objects that request event notification are now wrapped in a Java -InvocationProxy so that a standard interface is always presented to the JNI -EventProxy object.

- -

2.    The -EventProxy constructor accepts an Java InvocationProxy as the object that will -receive any callbacks for this set of events.

- -

3.    The -Java InvocationProxy has a method on it that will return the Variant class that -the EventProxy.

- -

 

- -

Developers can receive call back events in JWS other Java -launching programs without implementing any additional code.  They should be aware that their callback -methods may need to set the class loader. If they expect to create any objects.:

- -

      Public xxx -someHandler(Variant[] foo){

- -

            Thread.currentThread().setContextClassLoader(

- -

                  this.getClass().getClassLoader());

- -

            // do -something

- -

      }

- -

 

- -

There may still be a dual event queue issue in JWS -applications that needs to be looked at.

- -

 

- -
- - - - diff --git a/jacob/docs/EventCallbacks.html b/jacob/docs/EventCallbacks.html new file mode 100644 index 0000000..62562ea --- /dev/null +++ b/jacob/docs/EventCallbacks.html @@ -0,0 +1,87 @@ + + + +Jacob can register Java classes for MS application events or callbacks + +

Events

+Jacob can register Java classes for MS application events or callbacks. The normal flow for this is: + + +
    +
  1. Application thread creates an instance of the event handler and registers it with Jacob +
  2. The application continues on doing other work. +
  3. Some time later, the MS application takes some action and initiates the event callback. +
  4. The Java VM receives the event and spins up a new thread to handle it. +
  5. The Jacob jni EventProxy in the dll is called by the VM. +
  6. The Jacob jni EventProxy creates Variant objects to handle the parameters of the passed in event. +
  7. The Jacob jni EventProxy sends the name of the callback and the array of Variant objects to the Java InvocationProxy that was registered to catch events. +
  8. The Java InvocationProxy uses reflection to map the event name to a method name with the exact same name. +
  9. The Java InvocationProxy sends the message to the registered event handler and returns if the event handler is of type void (standard behavior). +
  10. The Java InvocationProxy sends the message to the registered event handler and returns the Variant that resulted from the call back to the Jacob jni EventProxy that then returns it to the windows calling program. +
+ +

SWING Issues

+Swing developers should note that this message comes in on a thread other than the event thread. +All objects receiving events that require user intervention or drawing in the UI should use +invokeLater() to post requests for actions onto the event queue. Failure to do so will +insure random failures in the GUI. +Java Web Start (JWS) and other launchers can have additional issues related to the class loader. +The Jacob C++ library uses FindClass() to find the Variant class when building the parameter list. +FindClass() uses the system class loader which includes only the classes specified at run time or +in the CLASSPATH. Most of the application classes in this situation live in an alternate set of +class loaders that were created when the launcher located and ran the application classes. This +means that the search for Variant will fail usually with the silent and immediate termination of +the Java application. The thread classloader probably can’t be used to try and find the class +because this new thread does not have a classloader associated with it other than the system class +loader. The end result is that the Variant class needs to be located via other means and that the +thread classloader should be set to be the context class loader of the event handler class. + +

1.8 and 1.9 behavior

+The Jacob EventProxy class has been modified (off of the 1.8 tree) so that it takes a two step approach towards fixing these problems. +
    +
  1. The EventProxy constructor now accepts an extra object, an instance of the Variant class. This gives the EventProxy a way to get to the Variant class and thus to its classloader. All of the callers of the constructor have been modified to pass a Variant object to the EventProxy without programmer intervention. +
  2. EventProxy first attempts to locate the Variant class using FindClass() +
  3. Failing that, it looks to see if a variant object had been passed in. If so, it calls class() and goes from there. +
  4. If all that fails, it logs a message and then fails in the spectacular fashion of the previous versions. +
+

+

1.10 behavior

+The Jacob EventProxy class has been modified so that it takes a different approach towards fixing this problem. +
    +
  1. All objects that request event notification are now wrapped in a Java InvocationProxy + so that a standard interface is always presented to the JNI EventProxy object. +
  2. The EventProxy constructor accepts any Java class. It wraps the class if it is not an + InvocationProxy or uses just the passed in object if it is an InvocationProxy. + The JNI layer talks to the InvocationProxy instead of talking directly to the event listener + as in previous releases. +
  3. The Java InvocationProxy has a method on it that will return the Variant class that the + EventProxy. The JNI code uses this method to acquire the class so that it can call newInstance(). +
+Developers can receive call back events in JWS other Java launching programs without implementing any additional code. They should be aware that their callback methods may need to set the class loader. If they expect to create any objects.: +
+      Public xxx someHandler(Variant[] foo){
+            Thread.currentThread().setContextClassLoader(
+                  this.getClass().getClassLoader());
+            // do something
+      }
+
+There may still be a dual event queue issue in JWS applications that needs to be looked at. + +

+

1.12 Experimental Behavior

+Release 1.12 adds experimental support for event handlers that accept java objects as parameters +to closer match the signature of the windows callback. New ActiveXDispatchEvents and +ActiveXInvocationProxy operate in tandem in the same way as DispatchEvents and InvocationProxy. +DispatchEvents overrides getInvocationProxy() to create a new ActiveXInvocationProxy in place +of the normal InvocationProxy. ActiveXInvocationProxy has its own invoke() method that uses +reflection to call back using java objects as parameters. +

+Issues with this approach +

+ + diff --git a/jacob/docs/ReleaseNotes.html b/jacob/docs/ReleaseNotes.html index 6b38c08..2611e5f 100644 --- a/jacob/docs/ReleaseNotes.html +++ b/jacob/docs/ReleaseNotes.html @@ -86,6 +86,13 @@

Tracked Changes

(pre-release 1) Dispatch static methods should throw runtime exceptions when null is passed in for the Dispatch object and when the Dispatch object is in an invalid state. + + 1702604 + (pre-release 6) Support java semantics in event callbacks. Create + ActiveXInvocationProxy and ActiveXDispatchEvents that provide the supplemental API. + See IETestActiveXProxy.java for an example. + +     diff --git a/jacob/src/com/jacob/activeX/ActiveXDispatchEvents.java b/jacob/src/com/jacob/activeX/ActiveXDispatchEvents.java new file mode 100644 index 0000000..a91d686 --- /dev/null +++ b/jacob/src/com/jacob/activeX/ActiveXDispatchEvents.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 1999-2004 Sourceforge JACOB Project. + * All rights reserved. Originator: Dan Adler (http://danadler.com). + * Get more information about JACOB at http://sourceforge.net/projects/jacob-project + * + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.jacob.activeX; + +import com.jacob.com.Dispatch; +import com.jacob.com.DispatchEvents; +import com.jacob.com.InvocationProxy; + +/** + * RELEASE 1.12 EXPERIMENTAL. + *

+ * Use this exactly like the DispatchEvents class. This class plugs in + * an ActiveXInvocationProxy instead of an InvocationProxy. It is the + * ActiveXInvocationProxy that implements the reflection calls and invoke + * the found java event callbacks. See ActiveXInvocationProxy for details. + * + * + */ +public class ActiveXDispatchEvents extends DispatchEvents { + + /** + * This is the most commonly used 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. + * @param sourceOfEvent Dispatch object who's MS app will generate callbacks + * @param eventSink Java object that wants to receive the events + */ + public ActiveXDispatchEvents(Dispatch sourceOfEvent, Object eventSink) { + super(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. + * @param sourceOfEvent Dispatch object who's MS app will generate callbacks + * @param eventSink Java object that wants to receive the events + * @param progId ??? + */ + public ActiveXDispatchEvents(Dispatch sourceOfEvent, Object eventSink, String progId) { + super(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. + *

+     * >ActiveXDispatchEvents de = 
+     * 			new ActiveXDispatchEvents(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 ActiveXDispatchEvents(Dispatch sourceOfEvent, Object eventSink, String progId, String typeLib)
+    {
+    	super(sourceOfEvent, eventSink, progId, typeLib);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.jacob.com.DispatchEvents#getInvocationProxy(java.lang.Object)
+     */
+	protected InvocationProxy getInvocationProxy(Object pTargetObject){
+		InvocationProxy newProxy = new ActiveXInvocationProxy();
+		newProxy.setTarget(pTargetObject);
+		return newProxy;
+	}
+
+}
diff --git a/jacob/src/com/jacob/activeX/ActiveXInvocationProxy.java b/jacob/src/com/jacob/activeX/ActiveXInvocationProxy.java
new file mode 100644
index 0000000..59f8e9f
--- /dev/null
+++ b/jacob/src/com/jacob/activeX/ActiveXInvocationProxy.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 1999-2004 Sourceforge JACOB Project.
+ * All rights reserved. Originator: Dan Adler (http://danadler.com).
+ * Get more information about JACOB at http://sourceforge.net/projects/jacob-project
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+package com.jacob.activeX;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import com.jacob.com.InvocationProxy;
+import com.jacob.com.NotImplementedException;
+import com.jacob.com.Variant;
+
+/**
+ * RELEASE 1.12 EXPERIMENTAL.
+ * 

+ * This class that lets event handlers receive events with all java + * objects as parameters. The standard Jacob event methods all accept an array of + * Variant objects. When using this class, you can set up your event methods + * as regular java methods with the correct number of parameters of the correct + * java type. This does NOT work for any event that wishes to accept a call + * back and modify the calling parameters to tell windows what to do. An example + * is when an event lets the receiver cancel the action by setting a boolean flag + * to false. The java objects cannot be modified and their values will not be passed + * back into the originating Variants even if they could be modified. + *

+ * This class acts as a proxy between the windows event callback mechanism and + * the Java classes that are looking for events. It assumes that + * all of the Java classes that are looking for events implement methods with the + * same names as the windows events and that the implemented methods native + * java objects of the type and order that match the windows documentation. + * The methods can return void or a Variant that + * will be returned to the calling layer. All Event methods that will be + * recognized by InvocationProxyAllEvents have the signature + * + * void eventMethodName(Object,Object...) + * or + * Object eventMethodName(Object,Object...) + */ +public class ActiveXInvocationProxy extends InvocationProxy { + + /* + * (non-Javadoc) + * @see com.jacob.com.InvocationProxy#invoke(java.lang.String, com.jacob.com.Variant[]) + */ + public Variant invoke(String methodName, Variant targetParameters[]) { + Variant mVariantToBeReturned = null; + if (mTargetObject == null){ + // structured programming guidlines say this return should not be up here + return null; + } + Class targetClass = mTargetObject.getClass(); + if (methodName == null){ + throw new IllegalArgumentException("InvocationProxy: missing method name"); + } + if (targetParameters == null){ + throw new IllegalArgumentException("InvocationProxy: missing Variant parameters"); + } + try { + Method targetMethod; + Object parametersAsJavaObjects[] = getParametersAsJavaObjects(targetParameters); + Class parametersAsJavaClasses[] = getParametersAsJavaClasses(parametersAsJavaObjects); + targetMethod = targetClass.getMethod(methodName, parametersAsJavaClasses); + if (targetMethod != null){ + // protected classes can't be invoked against even if they + // let you grab the method. you could do targetMethod.setAccessible(true); + // but that should be stopped by the security manager + Object mReturnedByInvocation = null; + mReturnedByInvocation = + targetMethod.invoke(mTargetObject,parametersAsJavaObjects); + if (mReturnedByInvocation == null){ + mVariantToBeReturned = null; + } else if (!(mReturnedByInvocation instanceof Variant)){ + mVariantToBeReturned = new Variant(mReturnedByInvocation); + } else { + mVariantToBeReturned = (Variant) mReturnedByInvocation; + } + } + } catch (SecurityException e) { + // what causes this exception? + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // this happens whenever the listener doesn't implement all the methods + } catch (IllegalArgumentException e) { + // we can throw these inside the catch block so need to re-throw it + Exception oneWeShouldToss = new IllegalArgumentException("Unable to map parameters for method "+methodName + + ": "+e.toString()); + oneWeShouldToss.printStackTrace(); + } catch (IllegalAccessException e) { + // can't access the method on the target instance for some reason + e.printStackTrace(); + } catch (InvocationTargetException e) { + // invocation of target method failed + e.printStackTrace(); + } + return mVariantToBeReturned; + + } + + /** + * creates a method signature compatible array of classes from an array of parameters + * @param parametersAsJavaObjects + * @return + */ + private Class[] getParametersAsJavaClasses(Object[] parametersAsJavaObjects) { + if (parametersAsJavaObjects == null){ + throw new IllegalArgumentException("This only works with an array of parameters"); + } + int numParameters = parametersAsJavaObjects.length; + Class parametersAsJavaClasses[] = new Class[numParameters]; + for ( int parameterIndex = 0 ; parameterIndex < numParameters; parameterIndex++){ + Object oneParameterObject = parametersAsJavaObjects[parameterIndex]; + if (oneParameterObject == null){ + parametersAsJavaClasses[parameterIndex] = null; + } else { + Class oneParameterClass = oneParameterObject.getClass(); + parametersAsJavaClasses[parameterIndex] = oneParameterClass; + } + } + return parametersAsJavaClasses; + } + + /** + * converts an array of Variants to their associated Java types + * @param targetParameters + * @return + */ + private Object[] getParametersAsJavaObjects(Variant[] targetParameters) { + if (targetParameters == null){ + throw new IllegalArgumentException("This only works with an array of parameters"); + } + int numParameters = targetParameters.length; + Object parametersAsJavaObjects[] = new Object[numParameters]; + for ( int parameterIndex = 0 ; parameterIndex < numParameters; parameterIndex++){ + Variant oneParameterObject = targetParameters[parameterIndex]; + if (oneParameterObject == null){ + parametersAsJavaObjects[parameterIndex] = null; + } else { + try { + parametersAsJavaObjects[parameterIndex] = oneParameterObject.toJavaObject(); + } catch (NotImplementedException nie){ + throw new IllegalArgumentException("Can't convert parameter "+parameterIndex+ + " type "+oneParameterObject.getvt() + +" to java object: "+nie.getMessage()); + } + } + } + return parametersAsJavaObjects; + } + + +} diff --git a/jacob/src/com/jacob/com/DispatchEvents.java b/jacob/src/com/jacob/com/DispatchEvents.java index 9c86a7f..ac7a671 100644 --- a/jacob/src/com/jacob/com/DispatchEvents.java +++ b/jacob/src/com/jacob/com/DispatchEvents.java @@ -108,7 +108,7 @@ public DispatchEvents(Dispatch sourceOfEvent, Object eventSink, String progId, S if (eventSink instanceof InvocationProxy) { mInvocationProxy = (InvocationProxy) eventSink; } else { - mInvocationProxy = new InvocationProxy(eventSink); + mInvocationProxy = getInvocationProxy(eventSink); } if (mInvocationProxy != null) { init3(sourceOfEvent, mInvocationProxy, progId, typeLib); @@ -121,6 +121,17 @@ public DispatchEvents(Dispatch sourceOfEvent, Object eventSink, String progId, S } /** + * returns an instance of the proxy configured with pTargetObject as its target + * @param pTargetObject + * @return InvocationProxy an instance of the proxy this DispatchEvents will send to the COM layer + */ + protected InvocationProxy getInvocationProxy(Object pTargetObject){ + InvocationProxy newProxy = new InvocationProxyAllVariants(); + newProxy.setTarget(pTargetObject); + return newProxy; + } + + /** * 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) @@ -155,7 +166,7 @@ protected void finalize() { */ public void safeRelease(){ if (mInvocationProxy!=null){ - mInvocationProxy.clearTarget(); + mInvocationProxy.setTarget(null); } mInvocationProxy = null; super.safeRelease(); diff --git a/jacob/src/com/jacob/com/InvocationProxy.java b/jacob/src/com/jacob/com/InvocationProxy.java index 5387186..d2bccec 100644 --- a/jacob/src/com/jacob/com/InvocationProxy.java +++ b/jacob/src/com/jacob/com/InvocationProxy.java @@ -19,9 +19,6 @@ */ package com.jacob.com; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - /** * @version $Id$ * @author joe @@ -31,136 +28,44 @@ * This means that EventProxy.cpp just calls invoke(String,Variant[]) * against the instance of this class. Then this class does * reflection against the event listener to call the actual event methods. - * All Event methods have the signature - * - * void eventMethodName(Variant[]) - * or - * Variant eventMethodName(Variant[]) + * The event methods can return void or return a Variant. Any + * value returned will be passed back to the calling windows module + * by the Jacob JNI layer. + *

+ * * The void returning signature is the standard legacy signature. * The Variant returning signature was added in 1.10 to support event handlers * returning values. * */ -public class InvocationProxy { +public abstract class InvocationProxy { /** * the object we will try and forward to. */ - Object mTargetObject = null; + protected Object mTargetObject = null; /** * 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){ super(); - if (JacobObject.isDebugEnabled()){ - JacobObject.debug( - "InvocationProxy: created for object "+pTargetObject); - } - mTargetObject = pTargetObject; - if (mTargetObject == null){ - throw new IllegalArgumentException("InvocationProxy requires a target"); - } - // JNI code apparently bypasses this check and could operate against - // protected classes. This seems like a security issue... - // maybe it was because JNI code isn't in a package? - if (!java.lang.reflect.Modifier.isPublic( - pTargetObject.getClass().getModifiers())){ - throw new IllegalArgumentException( - "InvocationProxy only public classes can receive event notifications"); - } } /** * The method actually invoked by EventProxy.cpp. * The method name is calculated by the underlying JNI code from the MS windows - * Callback function name. The method is assumed to take an array of Variant + * Callback function name. The method is assumed to take an array of Variant * objects. The method may return a Variant or be a void. Those are the only * two options that will not blow up. + *

+ * Subclasses that override this should make sure mTargetObject is not null before processing. * * @param methodName name of method in mTargetObject we will invoke - * @param targetParameter Variant[] that is the single parameter to the method + * @param targetParameters Variant[] that is the single parameter to the method */ - public Variant invoke(String methodName, Variant targetParameter[]){ - Variant mVariantToBeReturned = null; - if (mTargetObject == null){ - if (JacobObject.isDebugEnabled()){ - JacobObject.debug( - "InvocationProxy: received notification ("+methodName+") with no target set"); - } - // structured programming guidlines say this return should not be up here - return null; - } - Class targetClass = mTargetObject.getClass(); - if (methodName == null){ - throw new IllegalArgumentException("InvocationProxy: missing method name"); - } - if (targetParameter == null){ - throw new IllegalArgumentException("InvocationProxy: missing Variant parameters"); - } - try { - if (JacobObject.isDebugEnabled()){ - JacobObject.debug("InvocationProxy: trying to invoke "+methodName - +" on "+mTargetObject); - } - Method targetMethod; - targetMethod = targetClass.getMethod(methodName, - new Class[] {Variant[].class}); - if (targetMethod != null){ - // protected classes can't be invoked against even if they - // let you grab the method. you could do targetMethod.setAccessible(true); - // but that should be stopped by the security manager - Object mReturnedByInvocation = null; - mReturnedByInvocation = - targetMethod.invoke(mTargetObject,new Object[] {targetParameter}); - if (mReturnedByInvocation == null){ - // so we do something in this block - mVariantToBeReturned = null; - } else if (!(mReturnedByInvocation instanceof Variant)){ - throw new IllegalArgumentException( - "InvocationProxy: invokation of target method returned " - +"non-null non-variant object: "+mReturnedByInvocation); - } else { - mVariantToBeReturned = (Variant) mReturnedByInvocation; - } - } - } catch (SecurityException e) { - // what causes this exception? - e.printStackTrace(); - } catch (NoSuchMethodException e) { - // this happens whenever the listener doesn't implement all the methods - if (JacobObject.isDebugEnabled()){ - JacobObject.debug("InvocationProxy: listener ("+mTargetObject+") doesn't implement " - + methodName); - } - } catch (IllegalArgumentException e) { - e.printStackTrace(); - // we can throw these inside the catch block so need to re-throw it - throw e; - } catch (IllegalAccessException e) { - // can't access the method on the target instance for some reason - if (JacobObject.isDebugEnabled()){ - JacobObject.debug("InvocationProxy: probably tried to access public method on non public class" - + methodName); - } - e.printStackTrace(); - } catch (InvocationTargetException e) { - // invocation of target method failed - e.printStackTrace(); - } - return mVariantToBeReturned; - } + public abstract Variant invoke(String methodName, Variant targetParameters[]); /** * used by EventProxy.cpp to create variant objects in the right thread @@ -171,10 +76,26 @@ public Variant getVariant(){ } /** - * helper method used by DispatchEvents to help clean up and reduce references - * - */ - protected void clearTarget(){ - mTargetObject = null; - } + * Sets the target for this InvocationProxy. + * @param pTargetObject + * @throws IllegalArgumentException if target is not publicly accessable + */ + public void setTarget(Object pTargetObject){ + if (JacobObject.isDebugEnabled()){ + JacobObject.debug( + "InvocationProxy: setting target "+pTargetObject); + } + if (pTargetObject != null){ + // JNI code apparently bypasses this check and could operate against + // protected classes. This seems like a security issue... + // maybe it was because JNI code isn't in a package? + if (!java.lang.reflect.Modifier.isPublic( + pTargetObject.getClass().getModifiers())){ + throw new IllegalArgumentException( + "InvocationProxy only public classes can receive event notifications"); + } + } + mTargetObject = pTargetObject; + } + } diff --git a/jacob/src/com/jacob/com/InvocationProxyAllVariants.java b/jacob/src/com/jacob/com/InvocationProxyAllVariants.java new file mode 100644 index 0000000..e1b3aad --- /dev/null +++ b/jacob/src/com/jacob/com/InvocationProxyAllVariants.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 1999-2004 Sourceforge JACOB Project. + * All rights reserved. Originator: Dan Adler (http://danadler.com). + * Get more information about JACOB at http://sourceforge.net/projects/jacob-project + * + * 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.jacob.com; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * This class acts as a proxy between the windows event callback mechanism and + * the Java classes that are looking for events. It assumes that + * all of the Java classes that are looking for events implement methods with the + * same names as the windows events and that the implemented methods accept an + * array of variant objects. The methods can return void or a Variant that + * will be returned to the calling layer. All Event methods that will be + * recognized by InvocationProxyAllEvents have the signature + * + * void eventMethodName(Variant[]) + * or + * Variant eventMethodName(Variant[]) + */ +public class InvocationProxyAllVariants extends InvocationProxy { + + /* + * (non-Javadoc) + * @see com.jacob.com.InvocationProxy#invoke(java.lang.String, com.jacob.com.Variant[]) + */ + public Variant invoke(String methodName, Variant targetParameters[]) { + Variant mVariantToBeReturned = null; + if (mTargetObject == null){ + if (JacobObject.isDebugEnabled()){ + JacobObject.debug( + "InvocationProxy: received notification ("+methodName+") with no target set"); + } + // structured programming guidlines say this return should not be up here + return null; + } + Class targetClass = mTargetObject.getClass(); + if (methodName == null){ + throw new IllegalArgumentException("InvocationProxy: missing method name"); + } + if (targetParameters == null){ + throw new IllegalArgumentException("InvocationProxy: missing Variant parameters"); + } + try { + if (JacobObject.isDebugEnabled()){ + JacobObject.debug("InvocationProxy: trying to invoke "+methodName + +" on "+mTargetObject); + } + Method targetMethod; + targetMethod = targetClass.getMethod(methodName, + new Class[] {Variant[].class}); + if (targetMethod != null){ + // protected classes can't be invoked against even if they + // let you grab the method. you could do targetMethod.setAccessible(true); + // but that should be stopped by the security manager + Object mReturnedByInvocation = null; + mReturnedByInvocation = + targetMethod.invoke(mTargetObject,new Object[] {targetParameters}); + if (mReturnedByInvocation == null){ + mVariantToBeReturned = null; + } else if (!(mReturnedByInvocation instanceof Variant)){ + // could try and convert to Variant here. + throw new IllegalArgumentException( + "InvocationProxy: invokation of target method returned " + +"non-null non-variant object: "+mReturnedByInvocation); + } else { + mVariantToBeReturned = (Variant) mReturnedByInvocation; + } + } + } catch (SecurityException e) { + // what causes this exception? + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // this happens whenever the listener doesn't implement all the methods + if (JacobObject.isDebugEnabled()){ + JacobObject.debug("InvocationProxy: listener ("+mTargetObject+") doesn't implement " + + methodName); + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + // we can throw these inside the catch block so need to re-throw it + throw e; + } catch (IllegalAccessException e) { + // can't access the method on the target instance for some reason + if (JacobObject.isDebugEnabled()){ + JacobObject.debug("InvocationProxy: probably tried to access public method on non public class" + + methodName); + } + e.printStackTrace(); + } catch (InvocationTargetException e) { + // invocation of target method failed + e.printStackTrace(); + } + return mVariantToBeReturned; + + } +} diff --git a/jacob/src/com/jacob/com/Variant.java b/jacob/src/com/jacob/com/Variant.java index fbe2aa0..856b952 100644 --- a/jacob/src/com/jacob/com/Variant.java +++ b/jacob/src/com/jacob/com/Variant.java @@ -361,8 +361,7 @@ public void putStringRef(String in){ * * @throws IllegalArgumentException * if inVariant = null - * @param in - * a variant that is to be referenced by this variant + * @param inVariant A variant that is to be referenced by this variant */ public void putVariant(Variant inVariant) { if (inVariant == null) { @@ -387,7 +386,7 @@ public void putVariant(Variant inVariant) { * Used to get the value from a windows type of VT_VARIANT * or a jacob Variant type of VariantVariant. * Added 1.12 pre 6 - VT_VARIANT support is at an alpha level - * @returns Object a java Object that represents the content of the enclosed Variant + * @return Object a java Object that represents the content of the enclosed Variant */ public Object getVariant() { if ((this.getvt() & VariantVariant) == VariantVariant diff --git a/jacob/unittest/com/jacob/test/events/ExcelEventTest.java b/jacob/unittest/com/jacob/test/events/ExcelEventTest.java index f9c2c14..d1ce34b 100644 --- a/jacob/unittest/com/jacob/test/events/ExcelEventTest.java +++ b/jacob/unittest/com/jacob/test/events/ExcelEventTest.java @@ -74,13 +74,14 @@ public static void main(String args[]) { } /** - * dummy consturctor to create an InvocationProxy that wraps nothing + * Constructor so we can create an instance that implements invoke() */ public ExcelEventTest() { } /** - * override the invoke method to log all the events + * Override the invoke method to log all the events so that we don't have to + * implement all of the specific events. */ public Variant invoke(String methodName, Variant targetParameter[]) { System.out.println("Received event from Windows program" + methodName); diff --git a/jacob/unittest/com/jacob/test/events/IETest.java b/jacob/unittest/com/jacob/test/events/IETest.java index b166a1f..15fdffd 100644 --- a/jacob/unittest/com/jacob/test/events/IETest.java +++ b/jacob/unittest/com/jacob/test/events/IETest.java @@ -3,8 +3,7 @@ import com.jacob.com.*; import com.jacob.activeX.*; /** - * This test runs fine against jdk.1.5.0_05 and kills the VM - * under any variant of 1.4. + * This test runs fine against jdk 1.4 and 1.5 * * This demonstrates the new event handling code in jacob 1.7 * This example will open up IE and print out some of the events @@ -66,7 +65,7 @@ public void run() System.out.println("IETestThread: About to hookup event listener"); IEEvents ieE = new IEEvents(); - new DispatchEvents((Dispatch) ie, ieE,"InternetExplorer.Application.1"); + new DispatchEvents(ie, ieE,"InternetExplorer.Application.1"); System.out.println("IETestThread: Did hookup event listener"); /// why is this here? Was there some other code here in the past? Variant optional = new Variant(); @@ -110,7 +109,8 @@ public void run() } /** - * the events class must be publicly accessable for reflection to work + * The events class must be publicly accessable for reflection to work. + * The list of available events is located at http://msdn2.microsoft.com/en-us/library/aa768280.aspx */ public class IEEvents { @@ -134,6 +134,10 @@ public void DownloadComplete(Variant[] args) { System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): DownloadComplete "+args.length); } + public void NavigateError(Variant[] args) { + System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): NavigateError "+args.length); + } + public void NavigateComplete2(Variant[] args) { System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): NavigateComplete "+args.length); } @@ -179,6 +183,10 @@ public void PropertyChange(Variant[] args) { System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): PropertyChange "+args.length); } + public void SetSecureLockIcon(Variant[] args) { + System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): setSecureLockIcon "+args.length); + } + public void StatusTextChange(Variant[] args) { System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): StatusTextChange "+args.length); } @@ -186,6 +194,10 @@ public void StatusTextChange(Variant[] args) { public void TitleChange(Variant[] args) { System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): TitleChange "+args.length); } + + public void WindowClosing(Variant[] args) { + System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): WindowClosing "+args.length); + } } } diff --git a/jacob/unittest/com/jacob/test/events/IETestActiveXProxy.java b/jacob/unittest/com/jacob/test/events/IETestActiveXProxy.java new file mode 100644 index 0000000..6cd90a2 --- /dev/null +++ b/jacob/unittest/com/jacob/test/events/IETestActiveXProxy.java @@ -0,0 +1,207 @@ +package com.jacob.test.events; + +import com.jacob.activeX.ActiveXComponent; +import com.jacob.activeX.ActiveXDispatchEvents; +import com.jacob.com.ComThread; +import com.jacob.com.Dispatch; +import com.jacob.com.Variant; +/** + * This test runs fine against jdk 1.4 and 1.5 + * + * This demonstrates the new event handling code in jacob 1.7 + * This example will open up IE and print out some of the events + * it listens to as it havigates to web sites. + * contributed by Niels Olof Bouvin mailto:n.o.bouvin@daimi.au.dk + * and Henning Jae jehoej@daimi.au.dk + *

+ * May need to run with some command line options (including from inside Eclipse). + * Look in the docs area at the Jacob usage document for command line options. + */ + +class IETestActiveXProxy +{ + public static void main(String[] args) + { + // this line starts the pump but it runs fine without it + ComThread.startMainSTA(); + // remove this line and it dies + ///ComThread.InitMTA(true); + IETestActiveProxyThread aThread = new IETestActiveProxyThread(); + aThread.start(); + while (aThread.isAlive()){ + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // doen with the sleep + //e.printStackTrace(); + } + } + System.out.println("Main: Thread quit, about to quit main sta in thread " + +Thread.currentThread().getName()); + // this line only does someting if startMainSTA() was called + ComThread.quitMainSTA(); + System.out.println("Main: did quit main sta in thread " + +Thread.currentThread().getName()); + } +} + +class IETestActiveProxyThread extends Thread +{ + public static boolean quitHandled = false; + + public IETestActiveProxyThread(){ + super(); + } + + public void run() + { + // this used to be 5 seconds but sourceforge is slow + int delay = 5000; // msec + // paired with statement below that blows up + ComThread.InitMTA(); + ActiveXComponent ie = new ActiveXComponent("InternetExplorer.Application"); + try { + Dispatch.put(ie, "Visible", new Variant(true)); + Dispatch.put(ie, "AddressBar", new Variant(true)); + System.out.println("IETestActiveProxyThread: " + Dispatch.get(ie, "Path")); + Dispatch.put(ie, "StatusText", new Variant("My Status Text")); + + System.out.println("IETestActiveProxyThread: About to hookup event listener"); + IEEventsActiveProxy ieE = new IEEventsActiveProxy(); + new ActiveXDispatchEvents(ie, ieE,"InternetExplorer.Application.1"); + System.out.println("IETestActiveProxyThread: Did hookup event listener"); + /// why is this here? Was there some other code here in the past? + Variant optional = new Variant(); + optional.putNoParam(); + + System.out.println("IETestActiveProxyThread: About to call navigate to sourceforge"); + Dispatch.call(ie, "Navigate", new Variant("http://sourceforge.net/projects/jacob-project")); + System.out.println("IETestActiveProxyThread: Did call navigate to sourceforge"); + try { Thread.sleep(delay); } catch (Exception e) {} + System.out.println("IETestActiveProxyThread: About to call navigate to yahoo"); + Dispatch.call(ie, "Navigate", new Variant("http://groups.yahoo.com/group/jacob-project")); + System.out.println("IETestActiveProxyThread: Did call navigate to yahoo"); + try { Thread.sleep(delay); } catch (Exception e) {} + } catch (Exception e) { + e.printStackTrace(); + } catch (Throwable re){ + re.printStackTrace(); + } finally { + System.out.println("IETestActiveProxyThread: About to send Quit"); + ie.invoke("Quit", new Variant[] {}); + System.out.println("IETestActiveProxyThread: Did send Quit"); + } + // this blows up when it tries to release a DispatchEvents object + // I think this is because there is still one event we should get back + // "OnQuit" that will came after we have released the thread pool + // this is probably messed up because DispatchEvent object will have been + // freed before the callback + // commenting out ie.invoke(quit...) causes this to work without error + // this code tries to wait until the quit has been handled but that doesn't work + System.out.println("IETestActiveProxyThread: Waiting until we've received the quit callback"); + while (!quitHandled){ + try { Thread.sleep(delay/5);} catch (InterruptedException e) {} + } + System.out.println("IETestActiveProxyThread: Received the quit callback"); + // wait a little while for it to end + //try {Thread.sleep(delay); } catch (InterruptedException e) {} + System.out.println("IETestActiveProxyThread: about to call ComThread.Release in thread " + + Thread.currentThread().getName()); + + ComThread.Release(); + } + +/** + * The events class must be publicly accessable for reflection to work. + * The list of available events is located at http://msdn2.microsoft.com/en-us/library/aa768280.aspx + */ +public class IEEventsActiveProxy +{ + public void BeforeNavigate2(Dispatch pDisp, String url, Integer flags, + String targetFrameName, Variant postData, String headers, Boolean cancel) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): BeforeNavigate2 "+url); + } + + public void CommandStateChange(Integer command, Boolean enable) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): CommandStateChange "+command); + } + + public void DocumentComplete(Dispatch pDisp, String url) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): DocumentComplete "+url); + } + + public void DownloadBegin() { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): DownloadBegin "); + } + + public void DownloadComplete() { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): DownloadComplete "); + } + + public void NavigateComplete2(Dispatch pDisp, String url) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): NavigateComplete "+url); + } + + public void NavigateError(Dispatch pDispatch, String url, String targetFrameName, Integer statusCode, Boolean Cancel) { + System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): NavigateError "+statusCode); + } + + public void NewWindow2(Dispatch pDisp, Boolean cancel) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): NewWindow2 "+pDisp); + } + + public void OnFullScreen(Boolean fullScreen) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): OnFullScreen "+fullScreen); + } + + public void OnMenuBar(Boolean menuBar) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): OnMenuBar "+menuBar); + } + + public void OnQuit() { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): OnQuit "); + IETestActiveProxyThread.quitHandled = true; + } + + public void OnStatusBar(Boolean statusBar) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): OnStatusBar "+statusBar); + } + + public void OnTheaterMode(Boolean theaterMode) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): OnTheaterMode "+theaterMode); + } + + public void OnToolBar(Boolean onToolBar) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): OnToolBar "+onToolBar); + } + + public void OnVisible(Boolean onVisible) { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): onVisible "+ onVisible); + } + + public void ProgressChange() { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): ProgressChange "); + } + + public void PropertyChange() { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): PropertyChange "); + } + + public void SetSecureLockIcon(Integer secureLockIcon) { + System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): setSecureLockIcon "+secureLockIcon); + } + + public void StatusTextChange() { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): StatusTextChange "); + } + + public void TitleChange() { + System.out.println("IEEventsActiveProxy Received ("+Thread.currentThread().getName()+"): TitleChange "); + } + + public void WindowClosing(Boolean isChildWindow) { + System.out.println("IEEvents Received ("+Thread.currentThread().getName()+"): WindowClosing "+isChildWindow); + } +} + +} diff --git a/jacob/unittest/com/jacob/test/events/WordEventTest.java b/jacob/unittest/com/jacob/test/events/WordEventTest.java index addbedc..c6bb634 100644 --- a/jacob/unittest/com/jacob/test/events/WordEventTest.java +++ b/jacob/unittest/com/jacob/test/events/WordEventTest.java @@ -69,13 +69,13 @@ public static void main(String args[]) { } /** - * dummy consturctor to create an InvocationProxy that wraps nothing + * Constructor so we can create an instance that implements invoke() */ public WordEventTest() { } /** - * override the invoke method to loga ll the events + * override the invoke() method to log all the events without writing a bunch of code */ public Variant invoke(String methodName, Variant targetParameter[]) { System.out.println("Received event from Windows program" + methodName);