7

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

.h 文件 delares

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

处理程序是一个回调函数,在发生错误时调用,data_ptr 是任何传回给您的对象(状态),在我的应用程序中,它就是 this(当前对象)。

我能够在使用编组常量类型(如简单类型字符串、整数等)的 dll 中调用函数。但我不知道如何编组指向作为状态的 C# 对象的指针。

为了将对象引用从我通过在此处搜索找到的内容传递给 C 函数,否则我似乎需要一个结构类型才能编组到该函数,因此我创建了一个结构来保存我的对象:

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

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

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

我在 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出现错误:

该结构不能是值类。

我在这里做了很多搜索,在 Marshall 和 void* 上找到了各种各样的东西,但没有什么能帮助我让它工作

谢谢。

4

2 回答 2

3

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

所以你可以简单地传递IntPtr.Zero给代表。在您的错误处理程序委托中,您只需忽略data_ptrsince的值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();
    }
}
于 2012-04-12T07:33:57.993 回答
2

很可能有办法做到这一点,但我很久以前就放弃了。我想出的解决方案有点骇人听闻,但它非常有效,并且可以与我抛出的所有内容一起使用:

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

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

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

于 2012-04-12T05:49:21.407 回答