在使用自动布局类型编组结构时遇到了奇怪的行为。
例如:让我们采用一个简单的代码:
[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;不能作为一个无人管理的集合 结构体;没有有意义的大小或偏移量可以计算出来。
所以这意味着编译器在编译时不知道结构大小?我确信这个属性重新排序结构字段然后编译它,但它没有。
答案 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版本可以给你不同的结果。事实上,没有什么可以说同一结构的两个实例必须具有相同的大小。