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

时间:2012-04-12 05:44:16

标签: c# c++ c pinvoke

我正在尝试创建一个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时出现错误:

  

结构不能是值类。

我在这里做了很多搜索,在马歇尔找到了各种各样的东西,但是没有任何东西可以帮助我实现这个目标

感谢。

2 个答案:

答案 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 ++中编写一个小包装器,这有点痛苦,但我发现它比所有编组代码更有能力,更少痛苦。

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