【问题标题】:How do I call a function in a C++ Dll from C# that has void* callback and object parameter如何从 C# 调用具有 void* 回调和对象参数的 C++ Dll 中的函数
【发布时间】:2012-04-12 05:44:16
【问题描述】:

我正在尝试为 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;
}

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

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

我在 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* 上找到了各种各样的东西,但没有什么能帮助我让它工作

谢谢。

【问题讨论】:

    标签: c# c++ c pinvoke


    【解决方案1】:

    你让这变得比它需要的更复杂。您的 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();
        }
    }
    

    【讨论】:

    • 在这种情况下确实如此,事实上我并不需要将它传递给数据,因为它将在我的对象回调中可用,但我想在何时提供对函数的正确调用用户想要提供上下文。我猜数据的管理可以由 C# 对象处理以维护状态/上下文。所以我想这解决了我的问题,但我想如何或是否可以传递对象的原始问题仍未得到解答
    • 在我看来,您真的不想传递对 C# 对象的引用。请记住,托管对象可以通过 GC 移动。你要强迫这样的物体被钉住吗?原生代码能用这样的东西做什么?它是完全不透明的。您可以使用 ObjectIDGenerator 为每个对象获取一个唯一 ID,但我个人认为您的问题最好避开它!
    • 你说的有道理,我想答案是不要这样做/避免它,这可能不是不可能的,但你不应该这样做,谢谢
    【解决方案2】:

    很可能有办法做到这一点,但我很久以前就放弃了。我想出的解决方案有点老套,但它非常有效,并且适用于我提出的所有问题:

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

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

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

    【讨论】:

    • 谢谢 Chris,这很有用,但我目前没有编写托管 C++ 库的工具或知识,也许我可以在将来的某个时候弄清楚。我不认为您可以提供指向通用链接的链接以及如何使用它的文档?最后,如果这一切对我不起作用,解决方案是确保我在回调中需要的任何数据都必须添加到结构中并作为简单值编组?谢谢
    • PS:我的回调还有一个额外的问题,它工作正常,但即使我删除回调函数体中的所有代码,在函数结束时我也会遇到访问冲突(尝试读取或写入受保护的内存)
    • 其实我只是想通了:我的委托缺少属性 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]:stackoverflow.com/questions/4906931/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 2012-06-23
    • 1970-01-01
    • 1970-01-01
    • 2012-09-19
    • 1970-01-01
    相关资源
    最近更新 更多