如何将 C# 中的方法指针传递给 C 库?

Posted

技术标签:

【中文标题】如何将 C# 中的方法指针传递给 C 库?【英文标题】:How to pass a method pointer in C# to C library? 【发布时间】:2021-08-15 05:27:06 【问题描述】:

有人知道如何将 C# 中的方法转换为 void* 成员吗?

在下面的示例中,sigaction.sa_handler 是一个指定回调函数的 void* 成员。

环境:

VisualStudio 专业版 2019 16.9.4 dotnet 核心 SDK 3.1 WSL 上的 Ubuntu Tmds.Linux 0.5.0
    sigaction sigact = new sigaction();

    //Error CS0428  Cannot convert method group 'catcher' to non-delegate type 'void*'.Did you intend to invoke the method?
    sigact.sa_handler = catcher;

    //Error CS0030  Cannot convert type 'method' to 'void*'
    sigact.sa_handler = (void *)catcher;

    //Error CS0029  Cannot implicitly convert type 'SignalCallback' to 'void*'
    SignalCallback callback = new SignalCallback(catcher);
    sigact.sa_handler = callback;

    //Error CS0030  Cannot convert type 'SignalCallback' to 'void*'
    sigact.sa_handler = (void*)callback;

sigaction 的结构(来自 Tmds.Linux)

    public struct sigaction
    
        public sigset_t sa_mask;

        public void* sa_handler  get; set; 
        public void* sa_sigaction  get; set; 
        public int sa_flags  readonly get; set; 
        public void* sa_restorer  readonly get; set; 
    

完整源代码:

    using System;

    using static Tmds.Linux.LibC;
    using Tmds.Linux;
    using System.Runtime.InteropServices;
    
    delegate void SignalCallback(int sig);

    namespace example
    
        class Program
        
            static void catcher(int sig)
            
                Console.Write($"Signal catcher called for signal sig");
            

            static void timestamp(string str)
            
                Console.Write($"The time str is DateTime.Now");
            

            static unsafe int Main(string[] args)
            
                int result = 0;

                sigaction sigact = new sigaction();
                sigset_t waitset = new sigset_t();
                siginfo_t info = new siginfo_t();

                sigset_t* ptr = &sigact.sa_mask;
                sigemptyset(&sigact.sa_mask);
                sigact.sa_flags = 0;

                //Error CS0428  Cannot convert method group 'catcher' to non-delegate type 'void*'.Did you intend to invoke the method?
                sigact.sa_handler = catcher;

                //Error CS0029  Cannot implicitly convert type 'SignalCallback' to 'void*'
                SignalCallback callback = new SignalCallback(catcher);
                sigact.sa_handler = callback;

                sigaction(SIGALRM, &sigact, null);

                sigemptyset(&waitset);
                sigaddset(&waitset, SIGALRM);

                sigprocmask(SIG_BLOCK, &waitset, null);

                alarm(10);

                timestamp("before sigwaitinfo()");

                result = sigwaitinfo(&waitset, &info);

                if (result == SIGALRM)
                    Console.WriteLine($"sigwaitinfo() returned for signal info.si_signo");
                else
                
                    Console.WriteLine($"sigwait() returned code result");
                    Console.WriteLine($"sigwait() returned error number errno");
                    Console.WriteLine("sigwait() function failed");
                

                timestamp("after sigwaitinfo()");

                return result;
            
        
    

【问题讨论】:

【参考方案1】:

要从委托中获取void* 指针,您需要Marshal.GetFunctionPointerForDelegate。您必须确保委托在最终使用之前没有被垃圾回收。 您可以为此使用GC.KeepAlive

SignalCallback callback = new SignalCallback(catcher);
sigact.sa_handler = Marshal.GetFunctionPointerForDelegate(callback);

// do the rest of your stuff

// after it's last usage by unmanaged code
GC.KeepAlive(callback);

但是:

您确实应该更多地依赖 P/Invoke 来进行任何转换。在大多数情况下,它应该能够整理出void* 的转换,您只需要GC.KeepAlive

您当前的代码非常存在问题,因为您将托管指针全部转换为void*

例如,不是这个调用,而是从&托管指针到void*的转换

sigemptyset(&waitset);

你会使用

sigemptyset(ref waitset);

理想情况下,您的结构不应该使用属性,因为这使得在字段上定义属性变得更加困难。我注意到sigaction 的顺序似乎不正确,从我可以从快速的 Google 中得知。

你应该像这样定义sigaction

    public struct sigaction
    
        public SignalCallback sa_handler;
        public SignalAction sa_sigaction;
        public sigset_t sa_mask;
        public int sa_flags;
        public SignalRestorer sa_restorer;
    

【讨论】:

谢谢查理脸。我的代码中的 void* 是“libc”中的函数和结构,它是本机 C 代码。我需要使用它们从 linux 上的 C# 应用程序访问设备驱动程序。 我的意思是你不应该像那样传递结构和函数指针,这很危险,你应该使用 P/Invoke 来正确编组它们。 P/Invoke 会自动将ref SomeStruct 参数转换为void* 我明白了。现在我无法更改它,因为它是一个库,Tmds.Linux。我会将您的建议转发给 Tmds.Linux 的作者。 好的,那么至少您需要在传递值时使用fixed 固定值

以上是关于如何将 C# 中的方法指针传递给 C 库?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确地将浮点指针从 C 库传递到其 C# 包装器

如何将 Stucture 的地址传递给 C# 中的 Void 指针 [void*] 到 VC++ Dll

C ++ Boost库 - 将共享指针传递给函数

将二维数组指针传递给c ++中的函数[重复]

将图像指针传递给python中的DLL函数

从 C# 调用 C 函数,传递包含指针的结构