为什么不能用自动布局编组结构
本文关键字:结构 自动布局 不能 为什么 | 更新日期: 2023-09-27 18:06:53
我在编组具有自动布局类型的结构体时遇到了一个奇怪的行为。
例如:让我们看一个简单的代码:
[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>());
}
抛出异常:
Unhandled Exception: System。ArgumentException:类型不能将"StructAutoLayout"封送为非托管结构;无法计算有意义的大小或偏移量。
所以这意味着编译器在编译时不知道结构体的大小?我确信这个属性重新排序结构字段,然后编译它,但它没有。
这没有任何意义。编组用于互操作-当进行互操作时,双方必须在struct
的结构上完全同意。
当您使用auto layout时,您将有关结构布局的决定推迟给编译器。即使同一编译器的不同版本也可能导致不同的布局——这是一个问题。例如,一个编译器可能会这样使用:
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
将工作。然而,即使这样,它也只会返回非托管类型的大小,而不是托管类型的大小——这仍然可能有所不同。同样,两者在不同的系统上也可以不同(例如,作为64位应用程序运行时,IntPtr
在32位系统上是4个字节,在64位系统上是8个字节)。
另一个重要的一点是有多个级别的"编译"。在。net应用程序中。第一层,使用c#编译器,只是冰山一角——它是而不是处理自动布局结构中重新排序字段的部分。它只需将结构标记为"auto-layouted",就完成了。实际的布局是在通过CLI运行应用程序时处理的(规范不清楚JIT编译器是否处理这个问题,但我假设是这样)。但这与Marshal.SizeOf
甚至sizeof
都没有关系——这两个都是在运行时处理的。忘掉你从c++学到的一切吧——c#(甚至c++/CLI)是一个完全不同的野兽。
如果您需要分析托管内存,请使用内存分析器(如CLRProfiler)。但是要明白,您仍然是在一个非常特定的环境中分析内存——不同的系统或。net版本会给您不同的结果。事实上,没有规定同一结构体的两个实例必须具有相同的大小。