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

How to pass a method pointer in C# to C library?

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

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

环境:

    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;
            }
        }
    }

要从委托中获取 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;
    }