在 C# 中比较非托管函数指针的替代方法是什么(如何修复 CS8909)?

Dav*_*ner 7 c#

C#9 引入了非托管函数指针(例如delegate* unmanaged[Cdecl]<void>)。我一直在尝试这些以了解它们是如何工作的。升级到.NET 5.0.201后,我收到一个新警告:

error CS8909: Comparison of function pointers might yield an unexpected result, since pointers to the same function may be distinct.
Run Code Online (Sandbox Code Playgroud)

根据这个问题,多次引用托管函数可能并不总是产生相同的指针。

以下是可能触发此警告的代码类型示例:

// saves a function pointer in unmanaged code
[DllImport("mylib", CallingConvention = CallingConvention.Cdecl)]
static extern void set_func(delegate* unmanaged[Cdecl]<void> func);

// retrieves the function pointer that was saved above
[DllImport("mylib", CallingConvention = CallingConvention.Cdecl)]
static extern delegate* unmanaged[Cdecl]<void> get_func();

// unmanaged function implemented in C#
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
static void MyFunc() {
    ...
}

void Test() {
    // pass our unmanaged callers only function to unmanaged code
    set_func(&MyFunc);

    ...

    // sometime later we want to check if unmanaged code still has the same function
    // pointer or if it changed
    if (get_func() == (delegate* unmanaged[Cdecl]<void>)&MyFunc) { // this line triggers CS8909 warning
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

&MyFunc我可以看到,如果第一次调用产生的函数指针与第二次调用 产生的函数指针不同,代码将无法正常工作&MyFunc。所以这个警告是有道理的。

可以使用什么替代方法来执行不会导致 CS8909 警告指示的问题的同类测试?

例如,这安全吗?

// identical code from above is omitted for brevity, only Test() method is changed

static readonly delegate* unmanaged[Cdecl]<void> myFunc = &MyFunc;

void Test() {
    // pass our unmanaged callers only function to unmanaged code
    set_func(myFunc);

    ...

    // sometime later we want to check if unmanaged code still has the same function
    // pointer or if it changed
#pragma warning disable CS8909
    if (get_func() == myFunc) { // would still trigger warning CS8909 if it was enabled
#pragma warning restore CS8909
        ...
    }
Run Code Online (Sandbox Code Playgroud)

是否有其他替代方案可以避免禁用警告?

Mit*_*tch 0

将其分为三个部分。

  1. 获取委托的非托管指针并将结果保存在托管区域中
  2. 调用set_func并存储从 1 开始的结果。
  3. get_func根据 1 存储的结果检查 的结果。

这避免了这个问题,因为委托的转换不是双射,但比较它是完全有效的IntPtr

一个例子:

void Test() 
{
    // create a GC root to the delegate
    var myFuncDelegate = new Action(myFunc);
    var pMyFunc = Marshal.GetFunctionPointerForDelegate(myFuncDelegate);
    set_func(pMyFunc);

    // ...

    if (get_func() == pMyFunc) 
    {
        // ...
    }

    // Ensure we don't collect myFuncDelegate while unmanaged code has a reference
    GC.KeepAlive(myFuncDelegate);
}
Run Code Online (Sandbox Code Playgroud)