我正在尝试创建一个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;
}
编辑:我试图在[MarshalAs(UnmanagedType.Struct, SizeConst = 4)]
属性上放置一个属性:public object state
,但这会产生相同的错误,所以我删除了它,看起来无论如何都不会起作用。
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
时出现错误:
结构不能是值类。
我在这里做了很多搜索,在马歇尔找到了各种各样的东西,但是没有任何东西可以帮助我实现这个目标
感谢。
答案 0 :(得分:3)
你使这比它需要的更复杂。您的C#客户端不需要使用data_ptr
参数,因为C#委托已经具有用于维护this
指针的内置机制。
因此,您只需将IntPtr.Zero
传递给代理人即可。在您的错误处理程序委托中,您只需忽略data_ptr
的值,因为this
将可用。
如果你不遵循这个描述,这里有一个简短的程序来说明我的意思。请注意MyErrorHandler
如何作为错误处理程序的实例方法,并且可以修改实例数据。
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();
}
}
答案 1 :(得分:2)
很可能有办法做到这一点,但我很久以前就放弃了。我提出的解决方案略显苛刻,但它非常有效,可以与我所投入的所有内容一起使用:
C# - >托管C ++ - >本地电话
这样做你最终在托管C ++中编写一个小包装器,这有点痛苦,但我发现它比所有编组代码更有能力,更少痛苦。
老实说,尽管我有点希望有人给出一个不回避的答案,但我自己已经挣扎了很长一段时间。