调用非托管代码时,CLR 如何封送仅包含单个字段的结构?

Posted

技术标签:

【中文标题】调用非托管代码时,CLR 如何封送仅包含单个字段的结构?【英文标题】:How does the CLR marshal a structure containing only a single field when calling unmanaged code? 【发布时间】:2011-05-05 03:52:32 【问题描述】:

默认情况下,当通过 P/Invoke 调用非托管函数时,CLR 将如何封送仅包含单个字段但定义了多个方法、属性、运算符等的结构? 相关结构的简化版本可能如下所示:

public struct SimpleStruct

    private IntPtr _value;

    public SimpleStruct(IntPtr value)
    
       this._value = value;
    

    public int MyMethod()
    
       return 42;
    

这里调用的具体非托管函数是无关紧要的,所以为了讨论起见,假设从 Windows API 中得到一些简单的东西,比如这个:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool IsWindow(SimpleStruct hWnd);

特别是,我感兴趣的是结构是否像 IntPtr 类型一样被封送,或者 CLR 是否试图封送该结构,因为它会像具有多个字段的更复杂的结构一样。 我意识到“在幕后”,所有 原始类型(包括IntPtrInt32)都是作为结构实现的。但我不确定是否有一些内置的特殊情况可以处理这些表示原始类型的“已知”结构,而不是处理自定义的结构。

我了解只有类型中的字段是可访问的,并且任何方法、属性或事件都不能从非托管代码中访问,这正是我想要的。我希望将上述结构编组到非托管函数,就像重写声明以指定 IntPtr 类型的参数一样:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool IsWindow(IntPtr hWnd);

因此,将以下属性添加到结构的定义时,行为是否会发生变化?

[StructLayout(LayoutKind.Explicit)]
public struct OtherStruct

    [FieldOffset(0)]
    private IntPtr _value;

    public SimpleStruct(IntPtr value)
    
       this._value = value;
    

    public int MyMethod()
    
       return 42;
    

我只在模拟联合(即多个字段)时使用过FieldOffsetAttribute,但我在微软自己的一些代码中看到过这样做,表面上是为了实现我在上面询问的行为。

在只定义一个字段的情况下有什么不同吗?还是这只是让默认行为更加明确?

【问题讨论】:

由于只有一个字段的结构存储在内存中,与该字段相同,我无法想象 Interop 系统会对一个与另一个做任何不同的事情。跨度> @Gabe:这也是我的直觉。一切都“正常工作”。我对实现细节更加好奇,因此我以后可能需要支付潜在的性能或其他隐藏的惩罚。 This site 部分是让我质疑该假设的部分原因 - 请参阅“系统值类型”部分,该部分表明 System.Int32 被编组为 ELEMENT_TYPE_I4 “而不是作为包含 long 类型的单个成员的结构”。 文章中“你以同样的方式编组它们......”的部分向我表明,它只是说你可以使用System.Int32 将数据发送到期望的 C 函数一个long;您无需编写 C 函数即可获得 struct long 【参考方案1】:

关于你关于隐式/显式的问题:

这将导致完全相同的结果。默认LayoutKind(对于C#)是Sequential,具有这种布局的结构中的第一个元素是always at offset 0。 (以下元素的位置将取决于选择的包装,如第二个链接中所述。)

【讨论】:

以上是关于调用非托管代码时,CLR 如何封送仅包含单个字段的结构?的主要内容,如果未能解决你的问题,请参考以下文章

从 C# 调用 C++ dll。 “无法封送'返回值':托管/非托管类型组合无效。”

无法封送“返回值”:托管/非托管类型组合无效

是否在CLR上运行非托管Visual C ++?

CLR 在调用 c++ 函数时如何避免重击?

运行使用 /clr 构建的 DLL 的本机 C++ 应用程序时访问冲突

使用 std::mutex 关闭头文件的 clr 选项