diff --git a/jni/Dispatch.cpp b/jni/Dispatch.cpp index b4f69c2..154fb60 100644 --- a/jni/Dispatch.cpp +++ b/jni/Dispatch.cpp @@ -84,6 +84,11 @@ JNIEXPORT jobject JNICALL Java_com_jacob_com_Dispatch_QueryInterface return newAuto; } +/** + * starts up a new instance of the requested program (progId) + * and connects to it. does special code if the progid + * is of the alternate format (with ":") + **/ JNIEXPORT void JNICALL Java_com_jacob_com_Dispatch_createInstance (JNIEnv *env, jobject _this, jstring _progid) { @@ -146,6 +151,92 @@ doDisp: env->SetIntField(_this, jf, (unsigned int)pIDispatch); } +/** + * attempts to connect to an running instance of the requested program + * This exists solely for the factory method connectToActiveInstance. + **/ +JNIEXPORT void JNICALL Java_com_jacob_com_Dispatch_getActiveInstance + (JNIEnv *env, jobject _this, jstring _progid) +{ + jclass clazz = env->GetObjectClass(_this); + jfieldID jf = env->GetFieldID( clazz, DISP_FLD, "I"); + + const char *progid = env->GetStringUTFChars(_progid, NULL); + CLSID clsid; + HRESULT hr; + IUnknown *punk = NULL; + IDispatch *pIDispatch; + USES_CONVERSION; + LPOLESTR bsProgId = A2W(progid); + env->ReleaseStringUTFChars(_progid, progid); + // Now, try to find an IDispatch interface for progid + hr = CLSIDFromProgID(bsProgId, &clsid); + if (FAILED(hr)) { + ThrowComFail(env, "Can't get object clsid from progid", hr); + return; + } + // standard connection + //printf("trying to connect to running %ls\n",bsProgId); + hr = GetActiveObject(clsid,NULL, &punk); + if (!SUCCEEDED(hr)) { + ThrowComFail(env, "Can't get active object", hr); + return; + } + // now get an IDispatch pointer from the IUnknown + hr = punk->QueryInterface(IID_IDispatch, (void **)&pIDispatch); + if (!SUCCEEDED(hr)) { + ThrowComFail(env, "Can't QI object for IDispatch", hr); + return; + } + // GetActiveObject called AddRef + punk->Release(); + env->SetIntField(_this, jf, (unsigned int)pIDispatch); +} + +/** + * starts up a new instance of the requested program (progId). + * This exists solely for the factory method connectToActiveInstance. + **/ +JNIEXPORT void JNICALL Java_com_jacob_com_Dispatch_coCreateInstance + (JNIEnv *env, jobject _this, jstring _progid) +{ + jclass clazz = env->GetObjectClass(_this); + jfieldID jf = env->GetFieldID( clazz, DISP_FLD, "I"); + + const char *progid = env->GetStringUTFChars(_progid, NULL); + CLSID clsid; + HRESULT hr; + IUnknown *punk = NULL; + IDispatch *pIDispatch; + USES_CONVERSION; + LPOLESTR bsProgId = A2W(progid); + env->ReleaseStringUTFChars(_progid, progid); + // Now, try to find an IDispatch interface for progid + hr = CLSIDFromProgID(bsProgId, &clsid); + if (FAILED(hr)) { + ThrowComFail(env, "Can't get object clsid from progid", hr); + return; + } + // standard creation + hr = CoCreateInstance(clsid,NULL,CLSCTX_LOCAL_SERVER|CLSCTX_INPROC_SERVER,IID_IUnknown, (void **)&punk); + if (!SUCCEEDED(hr)) { + ThrowComFail(env, "Can't co-create object", hr); + return; + } + // now get an IDispatch pointer from the IUnknown + hr = punk->QueryInterface(IID_IDispatch, (void **)&pIDispatch); + if (!SUCCEEDED(hr)) { + ThrowComFail(env, "Can't QI object for IDispatch", hr); + return; + } + // CoCreateInstance called AddRef + punk->Release(); + env->SetIntField(_this, jf, (unsigned int)pIDispatch); +} + +/** + * release method + */ JNIEXPORT void JNICALL Java_com_jacob_com_Dispatch_release (JNIEnv *env, jobject _this) { diff --git a/jni/Dispatch.h b/jni/Dispatch.h index d486546..f25a6a0 100644 --- a/jni/Dispatch.h +++ b/jni/Dispatch.h @@ -41,6 +41,22 @@ JNIEXPORT jobject JNICALL Java_com_jacob_com_Dispatch_QueryInterface JNIEXPORT void JNICALL Java_com_jacob_com_Dispatch_createInstance (JNIEnv *, jobject, jstring); +/* + * Class: Dispatch + * Method: getActiveInstance + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_jacob_com_Dispatch_getActiveInstance + (JNIEnv *, jobject, jstring); + +/* + * Class: Dispatch + * Method: coCreateInstance + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_jacob_com_Dispatch_coCreateInstance + (JNIEnv *, jobject, jstring); + /* * Class: Dispatch * Method: release diff --git a/samples/com/jacob/samples/test/DispatchTest.java b/samples/com/jacob/samples/test/DispatchTest.java deleted file mode 100644 index 0a77a40..0000000 --- a/samples/com/jacob/samples/test/DispatchTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jacob.samples.test; - -import com.jacob.com.*; -import com.jacob.activeX.*; - -class DispatchTest -{ - public static void main(String[] args) - { - ComThread.InitSTA(); - - ActiveXComponent xl = new ActiveXComponent("Excel.Application"); - Dispatch xlo = xl.getObject(); - try { - System.out.println("version="+xl.getProperty("Version")); - System.out.println("version="+Dispatch.get(xlo, "Version")); - Dispatch.put(xlo, "Visible", new Variant(true)); - Dispatch workbooks = xl.getProperty("Workbooks").toDispatch(); - 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("a1 from excel:"+Dispatch.get(a1, "Value")); - System.out.println("a2 from excel:"+Dispatch.get(a2, "Value")); - Variant f = new Variant(false); - Dispatch.call(workbook, "Close", f); - } catch (Exception e) { - e.printStackTrace(); - } finally { - xl.invoke("Quit", new Variant[] {}); - ComThread.Release(); - } - } -} diff --git a/src/com/jacob/activeX/ActiveXComponent.java b/src/com/jacob/activeX/ActiveXComponent.java index 07e7702..e489858 100644 --- a/src/com/jacob/activeX/ActiveXComponent.java +++ b/src/com/jacob/activeX/ActiveXComponent.java @@ -37,7 +37,8 @@ import com.jacob.com.*; * the senese that it is used for creating Dispatch objects */ public class ActiveXComponent extends Dispatch { - /** + + /** * Normally used to create a new connection to a microsoft application. * The passed in parameter is the name of the program as registred * in the registry. It can also be the object name. @@ -64,6 +65,14 @@ public class ActiveXComponent extends Dispatch { super(dispatchToBeWrapped); } + /** + * only used by the factories + * + */ + private ActiveXComponent() { + super(); + } + /** * Probably was a cover for something else in the past. * Should be deprecated. @@ -73,6 +82,64 @@ public class ActiveXComponent extends Dispatch { return this; } + /** + * Most code should use the standard ActiveXComponent(String) contructor + * and not this factory method. This method exists for applications + * that need special behavior. + * Experimental in release 1.9.2. + *
+ * Factory that returns a Dispatch object wrapped around the result + * of a CoCreate() call. This differs from the standard constructor + * in that it throws no exceptions and returns null on failure. + *
+ * This will fail for any prog id with a ":" in it. + * + * @param pRequestedProgramId + * @return Dispatch pointer to the COM object or null if couldn't create + */ + public static ActiveXComponent createNewInstance(String pRequestedProgramId){ + ActiveXComponent mCreatedDispatch = null; + try { + mCreatedDispatch = new ActiveXComponent(); + mCreatedDispatch.coCreateInstanceJava(pRequestedProgramId); + } catch (Exception e){ + mCreatedDispatch =null; + if (JacobObject.isDebugEnabled()){ + JacobObject.debug("Unable to co-create instance of "+pRequestedProgramId); + } + } + return mCreatedDispatch; + } + + /** + * Most code should use the standard ActiveXComponent(String) contructor + * and not this factory method. This method exists for applications + * that need special behavior. + * Experimental in release 1.9.2. + *
+ * Factory that returns a Dispatch wrapped around the result + * of a getActiveObject() call. This differs from the standard constructor + * in that it throws no exceptions and returns null on failure. + *
+ * This will fail for any prog id with a ":" in it + * + * @param pRequestedProgramId + * @return Dispatch pointer to a COM object or null if wasn't already running + */ + public static ActiveXComponent connectToActiveInstance(String pRequestedProgramId){ + ActiveXComponent mCreatedDispatch = null; + try { + mCreatedDispatch = new ActiveXComponent(); + mCreatedDispatch.getActiveInstanceJava(pRequestedProgramId); + } catch (Exception e){ + mCreatedDispatch =null; + if (JacobObject.isDebugEnabled()){ + JacobObject.debug("Unable to attach to running instance of "+pRequestedProgramId); + } + } + return mCreatedDispatch; + } + /** * @see com.jacob.com.Dispatch#finalize() */ diff --git a/src/com/jacob/com/Dispatch.java b/src/com/jacob/com/Dispatch.java index 3d8d277..dadb062 100644 --- a/src/com/jacob/com/Dispatch.java +++ b/src/com/jacob/com/Dispatch.java @@ -150,11 +150,18 @@ public class Dispatch extends JacobObject * This constructor always creates a new windows/program object * because it is based on the CoCreate() windows function. *
+ * Fails silently if null is passed in as the program id + *
* @param requestedProgramId */ public Dispatch(String requestedProgramId) { programId = requestedProgramId; - createInstance(requestedProgramId); + if (programId != null && !"".equals(programId)){ + createInstance(requestedProgramId); + } else { + throw new IllegalArgumentException( + "Dispatch(String) does not accept null or an empty string as a parameter"); + } } /** @@ -163,10 +170,59 @@ public class Dispatch extends JacobObject * Windows CoCreate() call *
* This ends up calling CoCreate down in the JNI layer + *
+ * The behavior is different if a ":" character exists in the progId. In that + * case CoGetObject and CreateInstance (someone needs to describe this better) * * @param progid */ - protected native void createInstance(String progid); + private native void createInstance(String progid); + + /** + * native call getActiveInstance only used by the constructor with the same parm + * type. This probably should be private. It is the wrapper for the + * Windows GetActiveObject() call + *
+ * This ends up calling GetActiveObject down in the JNI layer + *
+ * This does not have the special behavior for program ids with ":" in them + * that createInstance has. + * + * @param progid + */ + private native void getActiveInstance(String progid); + + /** + * Wrapper around the native method + * @param progid + */ + protected void getActiveInstanceJava(String progid){ + this.programId = progid; + getActiveInstance(progid); + } + + /** + * native call coCreateInstance only used by the constructor with the same parm + * type. This probably should be private. It is the wrapper for the + * Windows CoCreate() call + *
+ * This ends up calling CoCreate down in the JNI layer + *
+ * This does not have the special behavior for program ids with ":" in them + * that createInstance has. + * + * @param progid + */ + private native void coCreateInstance(String progid); + + /** + * Wrapper around the native method + * @param progid + */ + protected void coCreateInstanceJava(String progid){ + this.programId = progid; + coCreateInstance(progid); + } /** * Return a different interface by IID string. diff --git a/unittest/com/jacob/com/ActiveXComponentFactoryTest.java b/unittest/com/jacob/com/ActiveXComponentFactoryTest.java new file mode 100644 index 0000000..a9dbcbb --- /dev/null +++ b/unittest/com/jacob/com/ActiveXComponentFactoryTest.java @@ -0,0 +1,60 @@ +package com.jacob.com; + +import com.jacob.activeX.ActiveXComponent; + +/** + * This exercises the two Dispatch factor methods that let you + * control whether you create a new running COM object or connect to an existing one + * + * -Djava.library.path=d:/jacob/release -Dcom.jacob.autogc=false -Dcom.jacob.debug=true + * + * @author joe + * + */ +public class ActiveXComponentFactoryTest { + public static void main(String args[]) throws Exception { + ComThread.InitSTA(true); + try { + System.out.println("This test only works if MS Word is NOT already running"); + String mApplicationId = "Word.Application"; + ActiveXComponent mTryConnectingFirst = ActiveXComponent.connectToActiveInstance(mApplicationId); + if (mTryConnectingFirst != null ){ + mTryConnectingFirst.invoke("Quit",new Variant[] {}); + System.out.println("Was able to connect to MSWord when hadn't started it"); + } else { + System.out.println("Correctly could not connect to running MSWord"); + } + System.out.println(" Word Starting"); + ActiveXComponent mTryStartingSecond = ActiveXComponent.createNewInstance(mApplicationId); + if (mTryStartingSecond == null){ + System.out.println("was unable to start up MSWord "); + } else { + System.out.println("Correctly could start MSWord"); + } + ActiveXComponent mTryConnectingThird = ActiveXComponent.connectToActiveInstance(mApplicationId); + if (mTryConnectingThird == null ){ + System.out.println("was unable able to connect to MSWord after previous startup"); + } else { + System.out.println("Correctly could connect to running MSWord"); + // stop it so we can fail trying to connect to a running + mTryConnectingThird.invoke("Quit",new Variant[] {}); + System.out.println(" Word stopped"); + } + Thread.sleep(2000); + ActiveXComponent mTryConnectingFourth = ActiveXComponent.connectToActiveInstance(mApplicationId); + if (mTryConnectingFourth != null ){ + System.out.println("Was able to connect to MSWord that was stopped"); + mTryConnectingFourth.invoke("Quit",new Variant[] {}); + } else { + System.out.println("Correctly could not connect to running MSWord"); + } + } catch (ComException e) { + e.printStackTrace(); + } finally { + System.out.println("About to sleep for 2 seconds so we can bask in the glory of this success"); + Thread.sleep(2000); + ComThread.Release(); + ComThread.quitMainSTA(); + } + } +} diff --git a/unittest/com/jacob/com/DispatchNullProgramId.java b/unittest/com/jacob/com/DispatchNullProgramId.java new file mode 100644 index 0000000..3861b28 --- /dev/null +++ b/unittest/com/jacob/com/DispatchNullProgramId.java @@ -0,0 +1,32 @@ +package com.jacob.com; + +/** + * This test verifies that the Dispatch object protects itself when + * the constructor is called with a null program id. + * Prior to this protection, the VM might crash.m + * @author joe + * + */ +public class DispatchNullProgramId { + + public static void main(String[] args) { + try { + String nullParam = null; + new Dispatch(nullParam); + System.out.println( + "the dispatch failed to protect itself from null program ids"); + } catch (IllegalArgumentException iae){ + System.out.println( + "the dispatch protected itself from null program ids"); + } + try { + String nullParam = ""; + new Dispatch(nullParam); + System.out.println( + "the dispatch failed to protect itself from empty string program ids"); + } catch (IllegalArgumentException iae){ + System.out.println( + "the dispatch protected itself from empty string program ids"); + } + } +} diff --git a/unittest/com/jacob/com/ExcelEventTest.java b/unittest/com/jacob/com/ExcelEventTest.java new file mode 100644 index 0000000..b3bf9c4 --- /dev/null +++ b/unittest/com/jacob/com/ExcelEventTest.java @@ -0,0 +1,50 @@ +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... + * -Djava.library.path=d:/jacob/release -Dcom.jacob.autogc=false + * -Dcom.jacob.debug=true + */ +public class ExcelEventTest { + + 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) { + + // 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 ExcelEventTest()); + } else { + de = new DispatchEvents(axc, new ExcelEventTest(), + pid,typeLibLocation); + } + if (de == null){ + System.out.println( + "No exception thrown but now dispatch returned for Excel events"); + } + // Yea! + System.out.println("Successfully attached to " + pid); + } catch (ComFailException e) { + e.printStackTrace(); + System.out.println("Failed to attach to " + pid + ": " + + e.getMessage()); + + } + } +}