3

我有一种情况,我用 C++/CLI 包装了一个 Native C++ DLL,以便最终在 C# 中使用。

有一些回调函数会在运行时引起一些问题。特别是,我得到以下异常:

ToadWrapTest.dll 中出现“System.Runtime.InteropServices.InvalidOleVariantTypeException”类型的未处理异常

附加信息:指定的 OLE 变体无效。

在这行代码(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;
    };

我制作这个“中间回调”的原因是试图规避当我尝试将委托从 C# 直接映射到本机 C++ 代码时引发的 Invalid variant 异常。作为一种尝试的解决方法,我在 C# 端声明了一个委托,并将该 funcptr 传递给 C++/CLI 包装器。然后,我将中介 funcptr 传递给本机 C++,然后将调用以菊花链形式连接在一起。

我所知道的是,这一切都适用于原生 C++ 世界。问题是将 void* 映射到托管世界。以下代码显示了回调的本机 C++ 版本:

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

如果有人可以在这里提供帮助,我将不胜感激。

4

2 回答 2

2

pInstance 和 pData 真的是 VARIANT 吗?如果是,我希望您的回调函数具有更强的类型:

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

如果是这种情况,在您的代码中,您应该能够查看实际的VARIANT以进行手动检查。如果您没有真正获得 VARIANT(即,您实际上只是获得 void * 指针),则不应尝试将它们转换为 C# 对象,因为它们没有内在含义。它们应该作为 IntPtr 传递。如果您知道它们应该具有某种其他类型的固有含义,则需要将它们编组为适当的类型。

于 2009-07-30T14:10:44.933 回答
2

非常感谢这个底座!我将下面的最终解决方案发布给任何必须处理像这样的第 3 方乐趣的人!请随时批评,因为我还没有完成代码优化。这可能仍然是一个迂回的解决方案。

首先,回调函数变成:

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

这个是大道具。如果您尝试从 void* 直接转换为 Object^,这将无法正常工作。使用 IntPtr 和我的中间回调:

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

我们终于在 C# 端获得了一个工作模型,并对对象进行了一些按摩:

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;
}

我提到“按摩”对象是因为委托并不特定于这个回调函数,而且我不知道 pData 将是什么对象,直到运行时(来自委托 POV)。由于这个问题,我必须对 pData 对象做一些额外的工作。我基本上不得不重载包装器中的构造函数来接受 IntPtr。提供代码是为了完全“清晰”:

DataPacketWrap (IntPtr dp)
{ 
DataPacket* pdp = (DataPacket*)(dp.ToPointer());
m_NativeDataPacket = pdp;
};
于 2009-07-31T02:56:40.750 回答