Unmanaged C++ calling managed C++ or other .NET components

After having worked MANY hours on getting a solid solution for this, I thought I'd document it for someone else's benefit. This isn't formatted pretty, but hopefully it conveys information. There are a lot of gotchas which I'll attempt to point out.

There are a few options:

That last option is the one I'll discuss since it seems to work anywhere the CLR works and doesn't have the DLL-hellishness of COM-registered components.

Hosting the CLR is actually quite simple. Google for CorBindToRuntimeEx and ICorRuntimeHost and you'll have what you need.

Next you'll want to get an IDispatch pointer to your .NET object. You'll be working with what feels like COM, smells like COM and looks like COM, but from all the stack traces I looked like, it is completely implemented in .NET--which really means you'll be working with interfaces.

To get an IDispatch to your .NET object, you first get an IDispatch pointer to the app domain:

#include <mscoree.h>
#include <atlbase.h>
#include <comutil.h>
CComPtr<_AppDomain> gDefAppDomain;
CComPtr<IUnknown> pUnknown;
//Retrieve the IUnknown default AppDomain
gCORRuntimeHost->GetDefaultDomain(&pUnknown);
pUnknown->QueryInterface(&gDefAppDomain.p);

Now it's time to create your internal object. For example, consider the following managed C++ object (NOTE: This technique works equally well on C# or VB.NET classes, not just on C++/CLI classes):

namespace MyObjNameSpace
{
   public __gc class CreateMyObjClass
    {

        //No whimping out on the easy param types in this example!! :)
        bool MyObjMethod(String* param1, int param2, [Out] String** retVal)
        ...

You need to get a pointer to the object, and then some way to call MyObjMethod.

CComPtr<_ObjectHandle> gpMyObjObjectHandle;
CComPtr<IDispatch> gpMyObjDisp;
gDefAppDomain->CreateInstance(_bstr_t(L"MyObjAssembly"), _bstr_t(L"MyObjNameSpace.CreateMyObjClass"), &gpMyObjObjectHandle);
CComVariant VntUnwrapped;
gpMyObjObjectHandle->Unwrap(&VntUnwrapped);
if (VntUnwrapped.vt != VT_DISPATCH)
{
    //something bad happened!--see below
}
else
    gpMyObjDisp = VntUnwrapped.pdispVal;

A couple of things to note (pay attention to this!):

Moving on, now you'll want to call a method on your object. Here is a basic function you can start with:

bool CallManagedFunction(CComPtr<IDispatch> pDisp, LPCTSTR szMethodName,
int iNoOfParams, VARIANT * pvArgs, VARIANT * pvRet)
{
DISPID dispid = 0;
_bstr_t bsMethodName = szMethodName;
LPOLESTR pbsMethodName = bsMethodName;

pDisp->GetIDsOfNames (IID_NULL, &pbsMethodName, 1, LOCALE_SYSTEM_DEFAULT, &dispid);

DISPPARAMS dispparamsArgs = {NULL, NULL, 0, 0};
dispparamsArgs.cArgs = iNoOfParams;
dispparamsArgs.rgvarg = pvArgs;

EXCEPINFO excep = {0};
UINT badArg = 0;

//Invoke the method on the Dispatch Interface
hr = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparamsArgs, pvRet, &excep, &badArg);
//do something with return code...
}

Some more hard-won hints:

An example of calling our method above would be:

VARIANT varArgs[3];
ZeroMemory(varArgs, sizeof(varArgs));

BSTR returnValue = NULL; //remember to SysFreeString this when it is returned
VariantInit(&varArgs[0]);
varArgs[0].vt = VT_BSTR | VT_BYREF;
varArgs[0].pbstrVal = &returnValue; //this is the out param in our example .NET method above

VariantInit(&varArgs[1]);
varArgs[1].vt = VT_I4;
varArgs[1].intVal = myIntValue;

VariantInit(&varArgs[2]);
varArgs[2].vt = VT_BSTR;
_bstr_t bs = myLPCTSTRValue;
varArgs[2].bstrVal = bs;

VARIANT varRet; //this will hold the return code (the bool in our .NET method)
VariantInit(&varRet);

CallManagedFunction(gpMyObjDisp, _T("MyObjMethod"), 3, varArgs, &varRet)

REMEMBER REMEMBER REMEMBER that params are passed in reverse order!

Other notes: I read somewhere that the main thread should be pumping messages so finalizers can get called from the garbage collector. Not sure if that's true or not. I also read you don't need to do any kind of CoInitialize, but I did just for good measure :) I THINK you can use the IDispatch interface from any thread--I read that the CLR will take care of all marshalling for you (I'm now always calling it from one thread--part of the pain from trying to figure out why I was getting E_NOINTERFACE).

Best of luck!

Popular Links