【问题标题】:Set C Function Pointer to C# Function将 C 函数指针设置为 C# 函数
【发布时间】:2013-10-31 22:48:42
【问题描述】:

我需要在 C 库中设置一个与 C# 函数相等的回调函数,并且在没有 dlopen 或 kernel32(这似乎是 windows/unix 特定的)的情况下无法找到一种方法。有谁知道这样做的方法吗?

问题:C 共享库暴露了函数指针,其值应该通过覆盖它们来设置。例如。

//C Code
extern void (*ptr_R_ShowMessage) (const char *)

当前的 c# 代码创建一个与此签名匹配的函数的委托,使用 marshal 类获取指向该委托的指针,然后用此值覆盖 C 指针。

//call to libdl or kernel32.dll 
IntPtr showPointer = GetFunctionAddress(hndl,"ptr_R_ShowMessage");
IntPtr newShowPointer = Marshal.GetFunctionPointerForDelegate(matchingDelegate);
Marshal.WriteIntPtr(showPointer, newShowPointer);

对 libdl 和 kernel32.dll 的要求会导致各种问题......理想情况下可以避免。

有谁知道如何使 C 库指针指向 C# 代码,而无需修改 C 代码或使用 GetFunctionAddress 动态加载?我认为这可能是不可能的,但似乎有可能。

【问题讨论】:

    标签: c# c interop function-pointers


    【解决方案1】:

    我不能 100% 确定这是否是您正在寻找的。​​p>

    在我们的例子中,我们有一个第三方 C Sdk,它为回调消息提供了几个钩子。回调调用在 C 代码中启动并期望调用 C 函数,但我们可以使用以下步骤将其挂钩到 C# 方法。 (这段代码有点老了,可能有更简洁和现代的方法可以在较新的 C# 迭代中完成同样的任务。

    首先,我们在 C# 类中声明一个回调委托,与 SDK 期望的 C 函数指针的签名兼容。

    例如,如果 C 代码期望回调具有以下签名的函数:

    void DecCallBack(int nPort, void* pBuf, int nSize, FRAME_INFO *pInfo, int nReserved1, int reserved 2);
    

    这是匹配的 C# 委托定义。请注意,“魔术”的发生要归功于 UnmanagedFunctionPointer 属性:

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    delegate void DecCallBack(int nPort, IntPtr pBuf, int nSize, ref FRAME_INFO frameInfo, int nReserved1, int nReserved2);
    

    一旦我们有了这个,我们声明一个DecCallBack类型的类成员,它将在我们的类中保存一个委托实例。

    static DecCallBack _decodeCallBack = null;
    

    接下来我们编写一个与委托签名兼容的处理程序方法。在这种情况下,我们将其命名为HandleDecData()

    private static void HandleDecData(int nPort, IntPtr pBuf, int nSize, ref FRAME_INFO frameInfo, int nReserved1, int nReserved2) {
    
       // Here we handle the callback,  this code is being called from an external C library
    
    }       
    

    接下来,在C#类的某处,我们需要初始化委托成员并hook我们的回调Handler(本例为静态方法HandleDecData()

    _decodeCallBack += new DecCallBack(HandleDecData);
    

    最后,我们将委托成员作为指向 C 的函数指针传递。 在我们的例子中,第三方 SDK 有一个 C PlayM4_SetDecCallBack() 函数,它期望在调用回调时调用函数指针 - 我们可以安全地传递我们类的委托成员。

     if( !PlayM4_SetDecCallBack(channel, _decodeCallBack) ) 
      throw new InvalidOperationException("Error setting DecCallBack");
    

    我希望这会有所帮助。

    【讨论】:

    • 天哪,绝对有帮助!不知道那个 UnmanagedFunctionPointerAttribute。也就是说,不幸的是,我使用的 SDK 没有公开用于设置值的“Set”,而只公开了函数指针变量本身。也就是我需要想办法获取共享库中函数指针的地址,这样我就可以用新的函数地址重写了。
    • 哦,我明白了 - 我认为您当前使用 GetProcAddress() 的方法很好。您可以将“hacky”代码封装在一个不错的 C# 属性下;)
    【解决方案2】:

    C 共享库不以这种方式公开函数指针,extern 表示该函数是在另一个 C 文件中定义的,并且在创建二进制对象期间链接器将在最终可执行文件中使用函数中的实际代码

    要解决此问题,您需要以某种方式在 C 项目中包含 C# 文件,以便 C 编译器将两者链接到二进制对象中,但这是不可能的

    当前代码尝试执行的操作称为“hack”,如果您想在没有 hack 的情况下实现类似功能:

    1. 在 C 代码中提供函数,该函数将接受指向要执行的函数的指针
    2. 在调用 ptr_R_ShowMessage 的 C 代码中使用来自 p1 的指针
    3. 在 C# 代码中从 P1 调用函数并提供作为参数调用的方法

    这是我发现的示例,它从外部库执行函数并将其提供为 C# 函数作为回调:http://tutorials.csharp-online.net/CSharp_Delegates_and_Events%E2%80%94Win32_callbacks

    【讨论】:

    • 您好伊利亚,感谢您的回复!我将在上面进行编辑以澄清一些事情,同意这是一个黑客,绝对不是我的......
    • 好的,所以C代码在调用ShowMessage函数的时候确实调用了ptr_R_ShowMessage的地址。这个指针也在 C 文件的其他地方定义。然而,要嵌入这个库,这个函数指针需要用一个新的函数地址重写,通过给它分配一个新的函数地址值(在这种情况下是一个具有相同签名的 c# 函数)。
    • 你知道如何从C#中获取原始函数指针的地址吗?
    • @evolvedmicrobe 你不需要这个,再次检查我的答案,你需要提供中介函数,该函数将从 C# 获取指针并将其存储在 C 中的变量中
    • 问题是我不控制我试图设置的 SDK,并且他们不公开将存储 R 变量的函数,他们只公开存储它的位置(作为值,但不是函数)。我可以得到指向C#函数的指针没问题,但是设置C函数是……
    【解决方案3】:

    我确认这在 C# 中是不可能的,因此需要与库加载函数进行交互。不过感谢两位的回答,绝对有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-20
      • 1970-01-01
      • 1970-01-01
      • 2012-11-21
      • 1970-01-01
      • 1970-01-01
      • 2012-04-03
      • 2011-08-15
      相关资源
      最近更新 更多