前段时间我用C ++编写了一个Windows DLL来封装我想在不同的C ++和Delphi编译器之间共享的一些功能。我总是喜欢COM允许我使用对象的方式,而不是在DLL中使用平面C API。因为对于这个DLL,我不需要COM的全部功能而且没有引用计数,我决定实现一个有点模仿COM的DLL接口。我有一个导出的C函数,它允许我创建一个对象并返回一个vtable接口指针。通过这个指针,我可以在C ++和Delphi中以编译器中立方式调用对象上的方法。当我完成使用接口指针时,我会调用它上的释放。
现在我想使用C#中的这个DLL,但我不知道如何导入该接口。此外,我有一个C#客户端必须实现的回调接口。以下是C ++接口头文件(稍微缩短了)。
struct CksReadParams
{
...
};
struct ICksAdapter
{
virtual ~ICksAdapter();
virtual int32_t __cdecl Release() = 0;
virtual int32_t __cdecl ReadValues(CksReadParams* params) = 0;
};
struct ICksAdapterCallback
{
virtual ~ICksAdapterCallback();
virtual void __cdecl OnConnected(int32_t clientID) = 0;
virtual void __cdecl OnData(int32_t clientID, const char* csvLine) = 0;
virtual void __cdecl OnLog(int32_t clientID, const char* message) = 0;
virtual void __cdecl OnError(int32_t clientID, int32_t code, const char* message) = 0;
};
extern "C"
{
typedef int(__cdecl *CA_PCreateAdapter)(ICksAdapterCallback* callback, ICksAdapter** adapter);
__declspec(dllexport) int32_t __cdecl CA_CreateAdapter(ICksAdapterCallback* callback, ICksAdapter** adapter);
}
你能给我一个关于如何在C#中使用它的草图,特别是如何实现回调接口,如何处理__cdecl调用约定以及如何处理回调中实际为UTF8编码的C ++字符串的char *参数?
答案 0 :(得分:1)
我编写了以下代码,似乎可以满足我的需求。它主要受到这篇博文Implementing an unmanaged C++ interface callback的启发。该代码主要基于Marshal.GetDelegateForFunctionPointer和Marshal.GetFunctionPointerForDelegate构建,它们从.NET 4.5开始可用,在我的情况下是可接受的。总而言之,您需要知道C ++对象的vtable,并且需要在非托管内存中为回调接口伪造vtable。
如果值得付出努力,我会留给读者。在我的情况下,它至少允许我重用我的DLL。下次我可能会坚持使用平面C API或首先使用COM。
class CksAdapterCallback
{
private IntPtr instance;
public CksAdapterCallback()
{
instance = Marshal.AllocHGlobal(IntPtr.Size * 6);
IntPtr vtblPtr = IntPtr.Add(instance, IntPtr.Size);
Marshal.WriteIntPtr(instance, vtblPtr);
Marshal.WriteIntPtr(vtblPtr, IntPtr.Zero); //dummy entry for the destructor
OnConnectedInternal = new OnConnectedDelegate(OnConnected);
Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnConnectedInternal));
OnDataInternal = new OnDataDelegate(OnData);
Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, 2*IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnDataInternal));
...
}
~CksAdapterCallback()
{
Marshal.FreeHGlobal(instance);
}
public IntPtr Instance
{
get { return instance; }
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void OnConnectedDelegate(IntPtr instance, Int32 clientID);
OnConnectedDelegate OnConnectedInternal;
void OnConnected(IntPtr instance, Int32 clientID)
{
...
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void OnDataDelegate(IntPtr instance, Int32 clientID, IntPtr data);
OnDataDelegate OnDataInternal;
void OnData(IntPtr instance, Int32 clientID, IntPtr data)
{
...
}
...
}
class CksAdapterProxy
{
private IntPtr instance;
public CksAdapterProxy(IntPtr instance)
{
this.instance = instance;
IntPtr vtblPtr = Marshal.ReadIntPtr(instance, 0);
IntPtr funcPtr = Marshal.ReadIntPtr(vtblPtr, 1 * IntPtr.Size);
ReleaseInternal = Marshal.GetDelegateForFunctionPointer<ReleaseDelegate>(funcPtr);
...
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate Int32 ReleaseDelegate(IntPtr instance);
ReleaseDelegate ReleaseInternal;
...
public Int32 Release()
{
return ReleaseInternal(instance);
}
...
}
public class CksAdapterTest
{
[DllImport("CKS\\CksAdapter.dll")]
[return: MarshalAs(UnmanagedType.I4)]
static extern int CA_CreateAdapter(IntPtr adapterCallback, out IntPtr adapter);
CksAdapterProxy adapter = null;
CksAdapterCallback adapterCallback = new CksAdapterCallback();
public CksAdapterTest()
{
IntPtr nativeAdapter = IntPtr.Zero;
int result = CA_CreateAdapter(adapterCallback.Instance, out nativeAdapter);
if(result == 0)
{
adapter = new CksAdapterProxy(nativeAdapter);
...
adapter.Release();
}
}
}