views:

449

answers:

2

I have a situation where I've wrapped a Native C++ DLL with C++/CLI for eventual use in C#.

There are a few callback functions that are causing some issues at run time. Particularly, I get the following exception:

An unhandled exception of type 'System.Runtime.InteropServices.InvalidOleVariantTypeException' occurred in ToadWrapTest.dll

Additional information: Specified OLE variant is invalid.

On this line of code (C++/CLI):

public delegate int ManagedCallbackFunction (Object^ inst, const Object^ data);
public delegate int UnManagedCallbackFunction (void* inst, const void* data);

ManagedCallbackFunction^ m_callbackFn;

int intermidiaryCallback(void * pInstance, const void * pData)
    { 
     void* temp = (void*)pData;
     System::IntPtr ip1 = IntPtr(pInstance);
     System::IntPtr ip2 = IntPtr(temp);
     Object^ oInst = Marshal::GetObjectForNativeVariant(ip1);
     Object^ oData = Marshal::GetObjectForNativeVariant(ip2);
     //invoke the callback to c#
     //return m_callbackFn::Invoke(oInst, oData);
     return 0;
    };

The reason I've made this "intermediary callback" was an attempt to circumvent the Invalid variant exception being thrown when I tried to directly map the delegate from C# to the native C++ code. As an attempted work-around, I declare a delegate on the C# side and pass that funcptr to the C++/CLI wrapper. I then pass the intermediary funcptr to the native C++ and just daisy chain the calls together.

What I know is that it all works in native C++ world. The problem is mapping the void* to the managed world. The following code shows the native C++ version of the callback:

int (*CallbackFunction) (void *inst, const void *data);

If anyone can help here, I'd really appreciate it.

+1  A: 

Are pInstance and pData really VARIANT? If they are, I would expect your callback function to be more strongly typed:

int (*CallbackFunction)(VARIANT *inst, VARIANT *data);

If that's the case, in your code you should be able to look at the actual VARIANT to hand check it. If you are not really getting VARIANTs (ie, you are really just getting void * pointers), you shouldn't try to turn them into C# objects since there is no inherent meaning to them. They should get passed through as IntPtr. If you know that they should have some other type of inherent meaning, you need to marshal them as appropriate types.

plinth
The callback is defined in a header provided by a 3rd party developer. If I could change it to VARIANT, I would. I will try to pass the IntPtr's to C# and then see what happens. Update forthcoming...
TomO
A: 

Big Thanks to plinth on this one! I am posting the final solution below to anyone else who has to deal with 3rd party fun like this one! Please feel free to critique, as I am not done optimizing the code. This may still be to roundabout a solution.

First, the callback functions became:

public delegate int ManagedCallbackFunction (IntPtr oInst, IntPtr oData);
public delegate int UnManagedCallbackFunction (void* inst, const void* data);
ManagedCallbackFunction^ m_callbackFn;

Big props on this one. It just plain won't work if you try to cast from void* directly to Object^. Using the IntPtr and my intermediary callback:

int intermidiaryCallback(void * pInstance, const void * pData)
{   
    void* temp = (void*)pData;
    return m_callbackFn->Invoke(IntPtr(pInstance), IntPtr(temp));
};

We finally get a working model on the C# side with some massaging of the objects:

public static int hReceiveTestMessage(IntPtr pInstance, IntPtr pData)
{
   // provide object context for static member function
   helloworld2 hw = (helloworld2)GCHandle.FromIntPtr(pInstance).Target;
   if (hw == null || pData == null)
   {
      Console.WriteLine("hReceiveTestMessage received NULL data or instance pointer\n");
      return 0;
   }

   // populate message with received data
   IntPtr ip2 = GCHandle.ToIntPtr(GCHandle.Alloc(new DataPacketWrap(pData)));
   DataPacketWrap dpw = (DataPacketWrap)GCHandle.FromIntPtr(ip2).Target;
   uint retval = hw.m_testData.load_dataSets(ref dpw);
   // display message contents
   hw.displayTestData();

   return 1;
}

I mention "massaging" the objects because the delegate is not specific to this callback function and I don't know what object pData will be until run time(from the delegates POV). Because of this issue, I have to do some extra work with the pData object. I basically had to overload the constructor in my wrapper to accept an IntPtr. Code is provided for full "clarity":

DataPacketWrap (IntPtr dp)
{ 
DataPacket* pdp = (DataPacket*)(dp.ToPointer());
m_NativeDataPacket = pdp;
};
TomO