/* * 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 */ #include "stdafx.h" #include #include "Dispatch.h" // Win32 support for Ole Automation #include #include #include #include #include #include "util.h" extern "C" { #define DISP_FLD "m_pDispatch" // extract a IDispatch from a jobject IDispatch *extractDispatch(JNIEnv *env, jobject arg) { jclass argClass = env->GetObjectClass(arg); jfieldID ajf = env->GetFieldID( argClass, DISP_FLD, "I"); jint anum = env->GetIntField(arg, ajf); IDispatch *v = (IDispatch *)anum; return v; } /** * This method finds an interface rooted on the passed in dispatch object. * This creates a new Dispatch object so it is NOT reliable * in the event callback thread of a JWS client where the root class loader * does not have com.jacob.com.Dispatch in its classpath */ JNIEXPORT jobject JNICALL Java_com_jacob_com_Dispatch_QueryInterface (JNIEnv *env, jobject _this, jstring _iid) { // get the current IDispatch IDispatch *pIDispatch = extractDispatch(env, _this); if (!pIDispatch) return NULL; // if we used env->GetStringChars() would that let us drop the conversion? const char *siid = env->GetStringUTFChars(_iid, NULL); USES_CONVERSION; LPOLESTR bsIID = A2W(siid); env->ReleaseStringUTFChars(_iid, siid); IID iid; HRESULT hr = IIDFromString(bsIID, &iid); if (FAILED(hr)) { ThrowComFail(env, "Can't get IID from String", hr); return NULL; } // try to call QI on the passed IID IDispatch *disp; hr = pIDispatch->QueryInterface(iid, (void **)&disp); if (FAILED(hr)) { ThrowComFail(env, "QI on IID from String Failed", hr); return NULL; } jclass autoClass = env->FindClass("com/jacob/com/Dispatch"); jmethodID autoCons = env->GetMethodID(autoClass, "", "(I)V"); // construct a Dispatch object to return // I am copying the pointer to java // jacob-msg 1817 - SF 1053871 : QueryInterface already called AddRef!! //if (disp) disp->AddRef(); jobject newAuto = env->NewObject(autoClass, autoCons, disp); 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_createInstanceNative (JNIEnv *env, jobject _this, jstring _progid) { jclass clazz = env->GetObjectClass(_this); jfieldID jf = env->GetFieldID( clazz, DISP_FLD, "I"); // if we used env->GetStringChars() would that let us drop the conversion? const char *progid = env->GetStringUTFChars(_progid, NULL); CLSID clsid; HRESULT hr; IUnknown *punk = NULL; IDispatch *pIDispatch; USES_CONVERSION; LPOLESTR bsProgId = A2W(progid); if (strchr(progid,':')) { env->ReleaseStringUTFChars(_progid, progid); // it's a moniker hr = CoGetObject(bsProgId, NULL, IID_IUnknown, (LPVOID *)&punk); if (FAILED(hr)) { ThrowComFail(env, "Can't find moniker", hr); return; } IClassFactory *pIClass; // if it was a clsid moniker, I may have a class factory hr = punk->QueryInterface(IID_IClassFactory, (void **)&pIClass); if (!SUCCEEDED(hr)) goto doDisp; punk->Release(); // try to create an instance hr = pIClass->CreateInstance(NULL, IID_IUnknown, (void **)&punk); if (FAILED(hr)) { ThrowComFail(env, "Can't create moniker class instance", hr); return; } pIClass->Release(); goto doDisp; } 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; } doDisp: // 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); } /** * 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_getActiveInstanceNative (JNIEnv *env, jobject _this, jstring _progid) { jclass clazz = env->GetObjectClass(_this); jfieldID jf = env->GetFieldID( clazz, DISP_FLD, "I"); // if we used env->GetStringChars() would that let us drop the conversion? 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_coCreateInstanceNative (JNIEnv *env, jobject _this, jstring _progid) { jclass clazz = env->GetObjectClass(_this); jfieldID jf = env->GetFieldID( clazz, DISP_FLD, "I"); // if we used env->GetStringChars() would that let us drop the conversion? 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) { jclass clazz = env->GetObjectClass(_this); jfieldID jf = env->GetFieldID( clazz, DISP_FLD, "I"); jint num = env->GetIntField(_this, jf); IDispatch *disp = (IDispatch *)num; if (disp) { disp->Release(); env->SetIntField(_this, jf, (unsigned int)0); } } static HRESULT name2ID(IDispatch *pIDispatch, const char *prop, DISPID *dispid, long lcid) { HRESULT hresult; USES_CONVERSION; LPOLESTR propOle = A2W(prop); hresult = pIDispatch->GetIDsOfNames(IID_NULL,(LPOLESTR*)&propOle,1,lcid,dispid); return hresult; } JNIEXPORT jintArray JNICALL Java_com_jacob_com_Dispatch_getIDsOfNames (JNIEnv *env, jclass clazz, jobject disp, jint lcid, jobjectArray names) { IDispatch *pIDispatch = extractDispatch(env, disp); if (!pIDispatch) return NULL; int l = env->GetArrayLength(names); int i; LPOLESTR *lps = (LPOLESTR *)CoTaskMemAlloc(l * sizeof(LPOLESTR)); DISPID *dispid = (DISPID *)CoTaskMemAlloc(l * sizeof(DISPID)); for(i=0;iGetObjectArrayElement(names, i); // if we used env->GetStringChars() would that let us drop the conversion? const char *nm = env->GetStringUTFChars(s, NULL); LPOLESTR nmos = A2W(nm); env->ReleaseStringUTFChars(s, nm); lps[i] = nmos; env->DeleteLocalRef(s); } HRESULT hr = pIDispatch->GetIDsOfNames(IID_NULL,lps,l,lcid,dispid); if (FAILED(hr)) { CoTaskMemFree(lps); CoTaskMemFree(dispid); char buf[1024]; strcpy_s(buf, "Can't map names to dispid:"); for(i=0;iGetObjectArrayElement(names, i); const char *nm = env->GetStringUTFChars(s, NULL); strcat_s(buf, nm); env->ReleaseStringUTFChars(s, nm); env->DeleteLocalRef(s); } ThrowComFail(env, buf, hr); return NULL; } jintArray iarr = env->NewIntArray(l); // SF 1511033 -- the 2nd parameter should be 0 and not i! env->SetIntArrayRegion(iarr, 0, l, dispid); CoTaskMemFree(lps); CoTaskMemFree(dispid); return iarr; } static char* BasicToCharString(const BSTR inBasicString) { char* charString = NULL; const size_t charStrSize = ::SysStringLen(inBasicString) + 1; if (charStrSize > 1) { charString = new char[charStrSize]; size_t len = ::wcstombs(charString, inBasicString, charStrSize); } else charString = ::_strdup(""); return charString; } static char* CreateErrorMsgFromResult(HRESULT inResult) { char* msg = NULL; ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, inResult,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &msg, 0, NULL); if (msg == NULL) msg = ::_strdup("An unknown COM error has occured."); return msg; } static char* CreateErrorMsgFromInfo(HRESULT inResult, EXCEPINFO* ioInfo, const char* methName) { char* msg = NULL; // If this is a dispatch exception (triggered by an Invoke message), // then we have to take some additional steps to process the error // message. if (inResult == DISP_E_EXCEPTION) { // Check to see if the server deferred filling in the exception // information. If so, make the call to populate the structure. if (ioInfo->pfnDeferredFillIn != NULL) (*(ioInfo->pfnDeferredFillIn))(ioInfo); // Build the error message from exception information content. char* source = ::BasicToCharString(ioInfo->bstrSource); char* desc = ::BasicToCharString(ioInfo->bstrDescription); const size_t MSG_LEN = ::strlen(methName) + ::strlen(source) + ::strlen(desc) + 128; msg = new char[MSG_LEN]; ::strncpy_s(msg, MSG_LEN, "Invoke of: ", strlen("Invoke of: ")); ::strncat_s(msg, MSG_LEN, methName, strlen(methName)); ::strncat_s(msg, MSG_LEN, "\nSource: ", strlen("\nSource: ")); ::strncat_s(msg, MSG_LEN, source, strlen(source)); ::strncat_s(msg, MSG_LEN, "\nDescription: ", strlen("\nDescription: ")); ::strncat_s(msg, MSG_LEN, desc, strlen(desc)); ::strncat_s(msg, MSG_LEN, "\n", strlen("\n")); delete source; delete desc; } else { char* msg2 = CreateErrorMsgFromResult(inResult); const size_t MSG_LEN = ::strlen(methName) + ::strlen(msg2) + 128; msg = new char[MSG_LEN]; ::strncpy_s(msg, MSG_LEN, "A COM exception has been encountered:\nAt Invoke of: ", strlen("A COM exception has been encountered:\nAt Invoke of: ")); ::strncat_s(msg, MSG_LEN, methName, strlen(methName)); ::strncat_s(msg, MSG_LEN, "\nDescription: ", strlen("\nDescription: ")); ::strncat_s(msg, MSG_LEN, msg2, strlen(msg2)); // jacob-msg 1075 - SF 1053872 : Documentation says "use LocalFree"!! //delete msg2; LocalFree(msg2); } return msg; } #define SETDISPPARAMS(dp, numArgs, pvArgs, numNamed, pNamed) \ {\ (dp).cArgs = numArgs; \ (dp).rgvarg = pvArgs; \ (dp).cNamedArgs = numNamed; \ (dp).rgdispidNamedArgs = pNamed; \ } #define SETNOPARAMS(dp) SETDISPPARAMS(dp, 0, NULL, 0, NULL) JNIEXPORT jobject JNICALL Java_com_jacob_com_Dispatch_invokev (JNIEnv *env, jclass clazz, jobject disp, jstring name, jint dispid, jint lcid, jint wFlags, jobjectArray vArg, jintArray uArgErr) { DISPPARAMS dispparams; EXCEPINFO excepInfo; IDispatch *pIDispatch = extractDispatch(env, disp); if (!pIDispatch) return NULL; int dispID = dispid; if (name != NULL) { const char *nm = env->GetStringUTFChars(name, NULL); HRESULT hr; if (FAILED(hr = name2ID(pIDispatch, nm, (long *)&dispID, lcid))) { char buf[1024]; sprintf_s(buf, 1024, "Can't map name to dispid: %s", nm); ThrowComFail(env, buf, -1); return NULL; } env->ReleaseStringUTFChars(name, nm); } int num_args = env->GetArrayLength(vArg); int i, j; VARIANT *varr = NULL; if (num_args) { varr = (VARIANT *)CoTaskMemAlloc(num_args*sizeof(VARIANT)); /* reverse args for dispatch */ for(i=num_args-1,j=0;0<=i;i--,j++) { VariantInit(&varr[j]); jobject arg = env->GetObjectArrayElement(vArg, i); VARIANT *v = extractVariant(env, arg); // no escape from copy? VariantCopy(&varr[j], v); env->DeleteLocalRef(arg); } } // prepare a new return value jclass variantClass = env->FindClass("com/jacob/com/Variant"); jmethodID variantCons = env->GetMethodID(variantClass, "", "()V"); // construct a variant to return jobject newVariant = env->NewObject(variantClass, variantCons); // get the VARIANT from the newVariant VARIANT *v = extractVariant(env, newVariant); DISPID dispidPropertyPut = DISPID_PROPERTYPUT; // determine how to dispatch switch (wFlags) { case DISPATCH_PROPERTYGET: // GET case DISPATCH_METHOD: // METHOD case DISPATCH_METHOD|DISPATCH_PROPERTYGET: { SETDISPPARAMS(dispparams, num_args, varr, 0, NULL); break; } case DISPATCH_PROPERTYPUT: case DISPATCH_PROPERTYPUTREF: // jacob-msg 1075 - SF 1053872 { SETDISPPARAMS(dispparams, num_args, varr, 1, &dispidPropertyPut); break; } } HRESULT hr = 0; jint count = env->GetArrayLength(uArgErr); if ( count != 0 ) { jint *uAE = env->GetIntArrayElements(uArgErr, NULL); hr = pIDispatch->Invoke(dispID,IID_NULL, lcid,(WORD)wFlags,&dispparams,v,&excepInfo,(unsigned int *)uAE); // SF 1689061 env->ReleaseIntArrayElements(uArgErr, uAE, 0); } else { hr = pIDispatch->Invoke(dispID,IID_NULL, lcid,(WORD)wFlags,&dispparams,v,&excepInfo, NULL); // SF 1689061 } if (num_args) { // to account for inouts, I need to copy the inputs back to // the java array after the method returns // this occurs, for example, in the ADO wrappers for(i=num_args-1,j=0;0<=i;i--,j++) { jobject arg = env->GetObjectArrayElement(vArg, i); VARIANT *v = extractVariant(env, arg); // reverse copy VariantCopy(v, &varr[j]); // clear out the temporary variant VariantClear(&varr[j]); env->DeleteLocalRef(arg); } } if (varr) CoTaskMemFree(varr); // check for error and display a somewhat verbose error message if (!SUCCEEDED(hr)) { // two buffers that may have to be freed later char *buf = NULL; char *dispIdAsName = NULL; // this method can get called with a name or a dispatch id // we need to handle both SF 1114159 if (name != NULL){ const char *nm = env->GetStringUTFChars(name, NULL); buf = CreateErrorMsgFromInfo(hr, &excepInfo, nm); env->ReleaseStringUTFChars(name, nm); } else { dispIdAsName = new char[256]; // get the id string _itoa_s (dispID, dispIdAsName, 256,10); //continue on mostly as before buf = CreateErrorMsgFromInfo(hr,&excepInfo,dispIdAsName); } // jacob-msg 3696 - SF 1053866 if(hr == DISP_E_EXCEPTION) { if(excepInfo.scode != 0) { hr = excepInfo.scode; } else { hr = _com_error::WCodeToHRESULT(excepInfo.wCode); } } ThrowComFail(env, buf, hr); if (buf) delete buf; if (dispIdAsName) delete dispIdAsName; return NULL; } return newVariant; } }