为什么不能用自动布局编组结构

时间:2015-08-06 13:51:13

标签: c# .net struct interop marshalling

在使用自动布局类型编组结构时遇到了奇怪的行为。

例如:让我们采用一个简单的代码:

[StructLayout(LayoutKind.Auto)]
public struct StructAutoLayout
{
    byte B1;
    long Long1;
    byte B2;
    long Long2;
    byte B3;
}
public static void Main()
{
    Console.WriteLine("Sizeof struct is {0}", Marshal.SizeOf<StructAutoLayout>());
}

它引发了异常:

  

未处理的异常:System.ArgumentException:Type   &#39; StructAutoLayout&#39;不能作为一个无人管理的集合   结构体;没有有意义的大小或偏移量可以计算出来。

所以这意味着编译器在编译时不知道结构大小?我确信这个属性重新排序结构字段然后编译它,但它没有。

1 个答案:

答案 0 :(得分:4)

没有任何意义。编组用于互操作 - 当进行互操作时,双方必须在struct的结构上完全同意

使用自动布局时,将有关结构布局的决定推迟到编译器。即使是同一编译器的不同版本也会导致不同的布局 - 这是一个问题。例如,一个编译器可能会使用它:

public struct StructAutoLayout
{
    byte B1;
    long Long1;
    byte B2;
    long Long2;
    byte B3;
}

而另一个人可能会这样做:

public struct StructAutoLayout
{
    byte B1;
    byte B2;
    byte B3;
    byte _padding;
    long Long1;
    long Long2;
}

在处理本机/非托管代码时,几乎没有涉及元数据 - 只是指针和值。另一方无法知道结构的实际布局,期望你事先商定的固定布局。

.NET倾向于让你被宠坏 - 几乎所有只是工作。当用C ++这样的东西进行干预时就不是这种情况了 - 如果你只是猜测你的方式,你很可能会得到一个通常有效的解决方案,但偶尔会崩溃你的整个应用程序。在使用非托管/本机代码执行任何操作时,请确保您完全理解您正在执行的操作 - 非托管互操作只是非常脆弱。

现在,Marshal类专为非托管互操作设计 。如果您阅读Marshal.SizeOf的文档,则明确说明

  

以字节为单位返回非托管类型的大小。

当然,

  

如果没有结构,可以使用此方法。布局必须是顺序的或明确的。

     

返回的大小是非托管类型的大小。对象的非托管和托管大小可以不同。对于字符类型,大小受应用于该类的CharSet值的影响。

如果类型不能可能被编组,那么Marshal.SizeOf会返回什么?这甚至都没有意义:)

在托管环境中询问类型或实例的大小没有任何意义。 “内存中的实际大小”就你所关注的而言是一个实现细节 - 它不是合同的一部分,而且它不是可以依赖的东西。如果运行时/编译器需要,它可以使每byte个77字节长,并且只要它只存储0到255之间的值,它就不会破坏任何合约。

如果您使用带有显式(或顺序)布局的struct,那么您将对非托管类型的布局方式有明确的约定,Marshal.SizeOf将起作用。然而,即便如此,它只会返回非托管类型的大小,而不是托管类型的大小 - 仍然可以有所不同。同样,两者在不同的系统上可能会有所不同(例如,IntPtr在32位系统上是4个字节,在64位应用程序上运行时在64位系统上是8个字节。)

另一个重点是.NET应用程序中有多个级别的“编译”。使用C#编译器的第一个层次只是冰山一角 - 而不是处理自动布局结构中的重新排序字段的部分。它只是将结构标记为“自动布局”,并且已完成。当您通过CLI运行应用程序时,将处理实际布局(规范尚不清楚JIT编译器是否处理该问题,但我会这样做)。但这与Marshal.SizeOf甚至sizeof无关 - 这两者仍然在运行时处理。忘记C ++中你知道的一切 - C#(甚至是C ++ / CLI)是一个完全不同的野兽。

如果需要配置托管内存,请使用内存分析器(如CLRProfiler)。但是要明白,你仍然在一个非常特定的环境中分析内存 - 不同的系统或.NET版本可以给你不同的结果。事实上,没有什么可以说同一结构的两个实例必须具有相同的大小。