在互操作方法中为"ref struct"参数传递"null"引用

本文关键字:quot struct 参数传递 null 引用 ref 操作方法 | 更新日期: 2023-09-27 18:02:09

我正在使用C#来调用DLL函数。

[DllImport("MyDLL.dll", SetLastError = true)]
public static extern uint GetValue(
        pHandle handle,
        ref somestruct a,
        ref somestruct b);

如何传递参数3的null引用?

当我尝试时,我得到了一个编译时错误:

无法从<null>转换为引用somestruct

我也试过IntPtr.Zero

在互操作方法中为"ref struct"参数传递"null"引用

您有两个选项:

  1. somestruct设为类,并将函数签名更改为:

    [DllImport("MyDLL.dll", SetLastError = true)]
    public static extern uint GetValue(
        pHandle handle, somestruct a, somestruct b);
    

    通常,这不会改变任何其他内容,除了可以传递null作为ab的值。

  2. 为该功能添加另一个过载,如下所示:

    [DllImport("MyDLL.dll", SetLastError = true)]
    public static extern uint GetValue(
        pHandle handle, IntPtr a, IntPtr b);
    

    现在,除了对somestruct:类型的对象调用ref之外,还可以用IntPtr.Zero调用函数

    GetValue(myHandle, ref myStruct1, ref myStruct2);
    GetValue(myHandle, IntPtr.Zero, IntPtr.Zero);
    

自.NET 5.0以来,出现了System.Runtime.CompilerServices.Unsafe.NullRef<T>((

GetValue(myHandle, ref myStruct1, ref myStruct2);
GetValue(myHandle, ref Unsafe.NullRef<somestruct>(), ref Unsafe.NullRef<somestruct>());

这个答案建议将SomeStruct作为一个类。我想展示这个想法的一个实现,它似乎很好地工作……即使您无法更改SomeStruct的定义(例如,当它是像System.Guid这样的预定义类型时;另请参阅此答案(。

  1. 定义一个通用包装类:

    [StructLayout(LayoutKind.Explicit)]
    public sealed class SomeStructRef
    {
        [FieldOffset(0)]
        private SomeStruct value;
        public static implicit operator SomeStructRef(SomeStruct value)
        {
            return new SomeStructRef { value = value };
        }
    }
    

    这里的基本思想与拳击相同。

  2. 将interop方法定义更改为以下内容:

    [DllImport("MyDLL.dll", SetLastError = true)]
    public static extern uint GetValue(
        pHandle handle,
        ref SomeStruct a,
        [MarshalAs(UnmanagedType.LPStruct)] SomeStructRef b);
    

第三个参数b将是"可以为null"的。由于SomeStructRef是引用类型,因此可以传递null引用。您也可以传递SomeStruct值,因为存在从SomeStructSomeStructRef的隐式转换运算符。并且(至少在理论上(,由于[StructLayout]/[FieldOffset]编组指令,SomeStructRef的任何实例都应该像SomeStruct的实际实例一样进行编组。

如果一个互操作专家能够验证这项技术的可靠性,我会很高兴。

另一个显而易见的解决方案是使用unsafe代码,并将interop方法声明更改为:

[DllImport("MyDLL.dll", SetLastError = true)]
unsafe public static extern uint GetValue(
        pHandle handle,
        ref somestruct a,
        somestruct* b);

请注意,该方法现在标记为unsafe,并且参数已从ref somestruct更改为somestruct*

这具有以下含义:

  • 只能从unsafe上下文内部调用该方法。例如:

    somestruct s;
    unsafe { GetValue(…, …, &s);   }  // pass a struct `s`
    unsafe { GetValue(…, …, null); }  // pass null reference
    
  • 为了使上述功能发挥作用,项目必须允许使用unsafe代码(在项目设置中,或通过/unsafe命令行编译器开关(。

  • 使用unsafe会导致IL代码无法验证。IIRC,这意味着加载此程序集需要完全信任(在某些情况下可能会出现问题(。