【问题标题】:Passing `null` reference for a `ref struct` parameter in interop method在互操作方法中为“ref struct”参数传递“null”引用
【发布时间】:2013-03-05 06:41:59
【问题描述】:

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

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

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

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

无法从 <null> 转换为 ref somestruct。

我也试过IntPtr.Zero

【问题讨论】:

标签: c# null interop parameter-passing value-type


【解决方案1】:

你有两个选择:

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

    现在您可以使用IntPtr.Zero 调用该函数,此外还可以将ref 用于somestruct 类型的对象:

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

【讨论】:

  • 这不是很常见的技术,非常有用!
【解决方案2】:

This answer 建议将SomeStruct 设为一个类。我想展示这个想法的实现,它看起来工作得很好……即使你无法更改SomeStruct 的定义(例如当它是像System.Guid 这样的预定义类型时;另请参阅this answer)。

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

    这里的基本思想与boxing相同。

  2. 将您的互操作方法定义更改为以下内容:

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

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

如果互操作专家能够验证此技术的合理性,我会很高兴。

【讨论】:

    【解决方案3】:

    另一个明显的解决方案是使用unsafe 代码并将互操作方法声明更改为:

    [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,这意味着加载此程序集将需要完全信任(在某些情况下可能会出现问题)。

    【讨论】:

      【解决方案4】:

      从 .NET 5.0 开始有 System.CompilerServices.Unsafe.NullRef<T>()

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-31
        • 2014-11-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-31
        • 2015-11-22
        • 1970-01-01
        相关资源
        最近更新 更多