/* * A C-callable bridge to the .NET object model * * C++/CIL is used to access the .NET object model via * System.Reflection et al. Here we provide C callable functions * to that functionality, which we then export via a COM * component. * * Note: the _only_ reason why we're going via COM and not simply * exposing the required via some DLL entry points, is that COM * gives us location independence (i.e., the RTS doesn't need * be told where this interop layer resides in order to hoik * it in, the CLSID suffices (provided the component has been * registered, of course.)) It is a bit tiresome to have play * by the .NET COM Interop's rules as regards argument arrays, * so we may want to revisit this issue at some point. * * [ But why not simply use C++/CIL throughout and provide direct * C-callable entry points to the relevant functionality, and avoid * COM interop altogether? Because we have to be able to * (statically) link with gcc-compiled code, and linking C++/CIL * and gcc-compiled object files doesn't work too well. ToDo: * revisit and track developments on this front. * ] */ #define _WIN32_DCOM #define COBJMACROS #include #include #include #ifndef _MSC_VER #include #include #include # if defined(COBJMACROS) && !defined(_MSC_VER) && 0 #define IErrorInfo_QueryInterface(T,r,O) (T)->lpVtbl->QueryInterface(T,r,O) #define IErrorInfo_AddRef(T) (T)->lpVtbl->AddRef(T) #define IErrorInfo_Release(T) (T)->lpVtbl->Release(T) #define IErrorInfo_GetSource(T,pbstr) (T)->lpVtbl->GetSource(T,pbstr) #define IErrorInfo_GetDescription(T,pbstr) (T)->lpVtbl->GetDescription(T,pbstr) # endif #define ISupportErrorInfo_QueryInterface(T,r,O) (T)->lpVtbl->QueryInterface(T,r,O) #define ISupportErrorInfo_AddRef(T) (T)->lpVtbl->AddRef(T) #define ISupportErrorInfo_Release(T) (T)->lpVtbl->Release(T) #define ISupportErrorInfo_InterfaceSupportsErrorInfo(T,iid) (T)->lpVtbl->InterfaceSupportsErrorInfo(T,iid) #endif #define WANT_UUID_DECLS #include "HsDotnet.h" #include "InvokerClient.h" /* Local prototypes */ static void genError( IUnknown* pUnk, HRESULT hr, char* loc, char** pErrMsg); static int startBridge(char**); static int fromVariant ( char* loc, int resTy, VARIANT* pVar, void* res, char** pErrMsg); static VARIANT* toVariant ( DotnetArg* p, VARIANT* pRes); /* Pointer to .NET COM component instance; instantiated on demand. */ static HsInvokeBridge* pBridge = NULL; /* convert a char* to a BSTR, copied from the HDirect comlib/ sources */ static HRESULT stringToBSTR( /*[in,ptr]*/const char* pstrz , /*[out]*/ BSTR* pbstr ) { int i; if (!pbstr) { return E_FAIL; } else { *pbstr = NULL; } if (!pstrz) { return S_OK; } i = MultiByteToWideChar(CP_ACP, 0, pstrz, -1, NULL, 0); if ( i < 0 ) { return E_FAIL; } *pbstr = SysAllocStringLen(NULL,i-1); if (*pbstr != NULL) { MultiByteToWideChar(CP_ACP, 0, pstrz, -1, *pbstr, i-1); // (*pbstr)[i]=0; return S_OK; } else { return E_FAIL; } } static char* bstrToString( BSTR bstr ) { int i,len; char *res; int blen; if (!bstr) { return NULL; } blen = SysStringLen(bstr); /* pass in NULL for the multi-byte arg in order to compute length first */ len = WideCharToMultiByte(CP_ACP, 0, bstr, blen, NULL, 0, NULL, NULL); if (len == 0) return NULL; /* Allocate string of required length. */ res = (char*)malloc(sizeof(char) * (len + 1)); if (!res) return NULL; i = WideCharToMultiByte(CP_ACP, 0, bstr, blen, res, (len+1), NULL, NULL); /* Poor error handling to map this to NULL. */ if ( i == 0 ) return NULL; /* Terminate and return */ res[i] = '\0'; return res; } static void freeArgs ( SAFEARRAY* psa ) { /* The argument SAFEARRAYs contain dynamically allocated * VARIANTs. Release the VARIANT contents and its memory here. */ long lb,ub; int i; HRESULT hr; hr = SafeArrayGetLBound(psa, 1, &lb); if (FAILED(hr)) { fprintf(stderr, "freeArgs: failed fetching lower bound\n"); SafeArrayDestroy(psa); return; } hr = SafeArrayGetUBound(psa, 1, &ub); if (FAILED(hr)) { fprintf(stderr, "freeArgs: failed fetching upper bound\n"); SafeArrayDestroy(psa); return; } for ( i = 0; i < (ub - lb); i++ ) { VARIANT vArg; VariantInit(&vArg); hr = SafeArrayGetElement(psa,(long*)&i,(void*)&vArg); if (FAILED(hr)) { #if 0 fprintf(stderr, "freeArgs: unable to fetch element %d (%d,%d,0x%08x)\n", i, ub,lb,hr); fflush(stderr); #endif break; } VariantClear(&vArg); } SafeArrayDestroy(psa); } static SAFEARRAY* marshalArgs ( DotnetArg* args, unsigned int n_args ) { SAFEARRAY *psa; SAFEARRAYBOUND rgsabound[1]; unsigned int i; long idxArr[1]; HRESULT hr; VARIANT var; rgsabound[0].lLbound = 0; rgsabound[0].cElements = n_args; psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound); for(i=0;i < n_args; i++) { idxArr[0] = i; VariantInit(&var); toVariant(&args[i],&var); hr = SafeArrayPutElement(psa, idxArr, (void*)&var); VariantClear(&var); } return psa; } /* * ***** Accessing the .NET object model ***** * * General remarks: * * - the functions report error conditions via their return value; a char*. * If NULL, the call was successful. If not, the returned string * contains the (dynamically allocated) error message. * * This unorthodox calling convetion is used to simplify the task * of interfacing to these funs from GHC-generated code. */ /* * Function: DN_invokeStatic() * * Given assembly and fully-qualified name of a static .NET method, * invoke it using the supplied arguments. * * Returns NULL on success, pointer to error message if an error. * */ char* DN_invokeStatic ( char *assemName, char *methName, DotnetArg *args, int n_args, int asBoxed, int resultTy, void *res) { SAFEARRAY* psa; VARIANT result; HRESULT hr; BSTR b_assemName; BSTR b_methName; char* errMsg = NULL; IUnknown *pUnk; long *arg_tys = NULL; int i; if (!pBridge && !startBridge(&errMsg)) { return errMsg; } arg_tys = (long*)malloc(sizeof(long) * (n_args)); if (arg_tys) { for (i=0;ivt == VT_ERROR) || (pVar->vt == VT_EMPTY && resTy != Dotnet_Unit) || (pVar->vt == VT_NULL && resTy != Dotnet_Unit)) ) { genError((IUnknown*)pBridge, E_FAIL, loc, pErrMsg); return FALSE; } #if 0 fprintf(stderr,"fromVar: %d\n", pVar->vt); fflush(stderr); #endif if (resTy < 0) { resTy = (int)Dotnet_Object; pVar->vt &= ~VT_ARRAY; #if 0 fprintf(stderr,"fromVar: %d %d\n", resTy, pVar->vt); fflush(stderr); #endif } VariantInit(&vNew); switch(resTy) { case Dotnet_Byte: case Dotnet_Char: hr = VariantChangeType (&vNew, pVar, 0, VT_UI1); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_UI1}", pErrMsg); return FALSE; } *((unsigned char*)res) = vNew.bVal; return 1; case Dotnet_Boolean: hr = VariantChangeType (&vNew, pVar, 0, VT_BOOL); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_BOOL}", pErrMsg); return 0; } *((unsigned char*)res) = vNew.bVal; return 1; case Dotnet_Int: hr = VariantChangeType (&vNew, pVar, 0, VT_INT); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_INT}", pErrMsg); return 0; } *((int*)res) = vNew.intVal; return 1; case Dotnet_Int8: hr = VariantChangeType (&vNew, pVar, 0, VT_I1); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_I1}", pErrMsg); return 0; } *((signed char*)res) = vNew.bVal; return 1; case Dotnet_Int16: hr = VariantChangeType (&vNew, pVar, 0, VT_I2); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_I2}", pErrMsg); return 0; } *((signed short*)res) = vNew.iVal; return 1; case Dotnet_Int32: hr = VariantChangeType (&vNew, pVar, 0, VT_I4); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_I4}", pErrMsg); return 0; } *((signed int*)res) = vNew.lVal; return 1; case Dotnet_Int64: hr = VariantChangeType (&vNew, pVar, 0, VT_I8); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_I8}", pErrMsg); return 0; } #ifdef _MSC_VER *((__int64*)res) = vNew.llVal; #else *((long long*)res) = vNew.lVal; #endif return 1; case Dotnet_Float: hr = VariantChangeType (&vNew, pVar, 0, VT_R4); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_R4}", pErrMsg); return 0; } *((float*)res) = vNew.fltVal; return 1; case Dotnet_Double: hr = VariantChangeType (&vNew, pVar, 0, VT_R8); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_R4}", pErrMsg); return 0; } *((double*)res) = vNew.dblVal; return 1; case Dotnet_Word8: hr = VariantChangeType (&vNew, pVar, 0, VT_UI1); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_UI1}", pErrMsg); return 0; } *((unsigned char*)res) = vNew.bVal; return 1; case Dotnet_Word16: hr = VariantChangeType (&vNew, pVar, 0, VT_UI2); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_UI2}", pErrMsg); return 0; } *((unsigned short*)res) = vNew.uiVal; return 1; case Dotnet_Word32: hr = VariantChangeType (&vNew, pVar, 0, VT_UI4); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_UI4}", pErrMsg); return 0; } *((unsigned int*)res) = vNew.ulVal; return 1; case Dotnet_Word64: hr = VariantChangeType (&vNew, pVar, 0, VT_UI8); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_UI8}", pErrMsg); return 0; } #ifdef _MSC_VER *((unsigned __int64*)res) = vNew.ullVal; #else *((unsigned long long*)res) = vNew.lVal; #endif return 1; case Dotnet_Ptr: hr = VariantChangeType (&vNew, pVar, 0, VT_BYREF); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_BYREF}", pErrMsg); return 0; } *((void**)res) = vNew.byref; return 1; case Dotnet_Unit: return 0; case Dotnet_Object: if ( pVar->vt == VT_BSTR ) { /* Special handling for strings. If the user has asked for * the string in object form, give him/her that. */ VARIANT res; VariantInit(&res); hr = HsInvokeBridge_NewString(pBridge, pVar->bstrVal, &res); if (SUCCEEDED(hr)) { pVar = &res; } } hr = VariantChangeType (&vNew, pVar, 0, VT_UNKNOWN); if ( FAILED(hr) ) { genError(NULL, hr, "HsInvoke.fromVariant{VT_UNKNOWN}", pErrMsg); return 0; } *((IUnknown**)res) = vNew.punkVal; return 1; case Dotnet_String: hr = VariantChangeType (&vNew, pVar, 0, VT_BSTR); if (FAILED(hr)) { genError(NULL, hr, "HsInvoke.fromVariant{VT_BSTR}", pErrMsg); return 0; } /* Storage is allocated by malloc(), caller is resp for freeing. */ *((char**)res) = bstrToString(vNew.bstrVal); return 1; } return 0; } /* * Function: toVariant() * * Convert a DotnetArg into a VARIANT. The VARIANT * is dynamically allocated. * * The result is the pointer to the filled-in VARIANT structure; * NULL if allocation failed. * */ static VARIANT* toVariant ( DotnetArg* p, VARIANT *v) { int is_array = 0; if (!v) return NULL; VariantInit(v); #if 0 fprintf(stderr,"marsh-0: %d\n", p->arg_type); fflush(stderr); #endif if (p->arg_type < 0) { is_array = 1; p->arg_type = Dotnet_Object; } // -(p->arg_type); } switch (p->arg_type) { case Dotnet_Byte: v->vt = VT_UI1; v->bVal = p->arg.arg_byte; break; case Dotnet_Char: v->vt = VT_UI1; v->bVal = p->arg.arg_char; break; case Dotnet_Boolean: v->vt = VT_BOOL; v->boolVal = p->arg.arg_bool; break; case Dotnet_Int: v->vt = VT_INT; v->intVal = p->arg.arg_int; break; case Dotnet_Int8: v->vt = VT_I1; v->bVal = p->arg.arg_int8; break; case Dotnet_Int16: v->vt = VT_I2; v->iVal = p->arg.arg_int16; break; case Dotnet_Int32: v->vt = VT_I4; v->lVal = p->arg.arg_int32; break; case Dotnet_Int64: v->vt = VT_I8; #ifdef _MSC_VER v->llVal = p->arg.arg_int64; #else (v->llVal) = p->arg.arg_int64; #endif break; case Dotnet_Float: v->vt = VT_R4; v->fltVal = p->arg.arg_float; break; case Dotnet_Double: v->vt = VT_R8; v->dblVal = p->arg.arg_double; break; case Dotnet_Word8: v->vt = VT_UI1; v->bVal = p->arg.arg_word8; break; case Dotnet_Word16: v->vt = VT_UI2; v->uiVal = p->arg.arg_word16; break; case Dotnet_Word32: v->vt = VT_UI4; v->ulVal = p->arg.arg_word32; break; case Dotnet_Word64: v->vt = VT_UI8; #ifdef _MSC_VER v->ullVal = p->arg.arg_word64; #else v->ullVal = (p->arg.arg_word64); #endif break; case Dotnet_Ptr: v->vt = VT_BYREF; v->byref = p->arg.arg_ptr; break; case Dotnet_Unit: v->vt = VT_EMPTY; break; case Dotnet_Object: v->vt = VT_UNKNOWN; v->punkVal = (IUnknown*)p->arg.arg_obj; break; case Dotnet_String: { BSTR b; HRESULT hr; v->vt = VT_BSTR; hr = stringToBSTR((const char*)p->arg.arg_str,&b); v->bstrVal = b; break; } } #if 0 fprintf(stderr,"marsh-1: %d %d\n", v->vt, is_array); fflush(stderr); if (is_array) { v->vt |= VT_ARRAY; v->punkVal = (IUnknown*)p->arg.arg_obj; } fprintf(stderr,"marsh-2: %d %d\n", v->vt, is_array); fflush(stderr); #endif return v; }