如何从C#调用具有void * callback和object参数的C ++ Dll中的函数

我正在尝试创建一个C dll的包装器,我试图调用一个具有回调函数的函数,接收一个对象作为传回的指针。

.h文件delares

extern int SetErrorHandler(void (*handler) (int, const char*, void*), void *data_ptr); 

处理程序是一个回调函数,在发生错误时调用,data_ptr是传递给你的任何对象(状态),在我的应用程序就是这个(当前对象)的情况下。

我能够在一个dll中调用函数,它使用编组的常量类型,如简单类型字符串,整数等。但我无法弄清楚如何只是编写一个指向C#对象的指针,这是一个状态。

为了通过在这里搜索我已经找到的对象引用传递对象引用,否则似乎我需要一个结构类型来能够编组该函数,所以我创建了一个结构来保存我的对象:

 [StructLayout(LayoutKind.Sequential)] struct MyObjectState { public object state; } 

编辑:我试图在public object state属性上放置一个属性: [MarshalAs(UnmanagedType.Struct, SizeConst = 4)] ,但这会产生相同的错误,所以我删除它,似乎它无论如何都会工作。

该struct包含一个对象属性,用于保存回调函数的任何对象。

我在C#中声明了委托如下:

 delegate void ErrorHandler(int errorCode, IntPtr name, IntPtr data); 

然后我在C#中声明了导入函数,如下所示:

 [DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)] static extern int SetErrorHandler handler, IntPtr data); 

然后我在我的C#代码中创建了一个回调函数:

 void MyErrorHandler(int errorCode, IntPtr name, IntPtr data) { var strName = Marshal.PtrToStringAnsi(name); var state = new MyObjectState(); Marshal.PtrToStructure(data, state); Console.WriteLine(strName); } 

我可以调用库函数,如下所示:

 var state = new MyObjectState() { state = this }; IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(state)); Marshal.StructureToPtr(state, pStruct, true); int ret = SetErrorHandler(MyErrorHandler, pStruct); 

调用工作和回调函数被调用,但我无法访问回调函数中的数据,当我尝试Marshal.PtrToStructure我收到一个错误:

结构不能是值类。

我在这里做了很多搜索,在马歇尔找到了各种各样的东西并且无效*但是没有任何东西帮助我让它发挥作用

谢谢。

你使它变得比它需要的更复杂。 您的C#客户端不需要使用data_ptr参数,因为C#委托已经具有用于维护this指针的内置机制。

因此,您只需将IntPtr.Zero传递给委托即可。 在您的error handling程序委托中,您只需忽略data_ptr的值,因为this将是可用的。

如果你不遵循这个描述,这里有一个简短的程序来说明我的意思。 请注意MyErrorHandler是一个充当error handling程序的实例方法,并且可以修改实例数据。

 class Wrapper { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate void ErrorHandler(int errorCode, string name, IntPtr data); [DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)] static extern int SetErrorHandler(ErrorHandler handler, IntPtr data); void MyErrorHandler(int errorCode, string name, IntPtr data) { lastError = errorCode; lastErrorName = name; } public Wrapper() { SetErrorHandler(MyErrorHandler, IntPtr.Zero); } public int lastError { get; set; } public string lastErrorName { get; set; } } class Program { static void Main(string[] args) { Wrapper wrapper = new Wrapper(); } } 

很可能有办法做到这一点,但我很久以前就放弃了。 我提出的解决方案略显苛刻,但它非常有效并适用于我抛出的所有内容:

C# – >托管C ++ – >本机调用

这样做你最终在托管C ++中编写一个小包装器,这是一种痛苦,但我发现它比所有编组代码更有能力,更少痛苦。

老实说,虽然我有点希望有人给出一个不回避的答案,但我自己已经挣扎了很长一段时间。