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

本文关键字:结构 自动布局 不能 为什么 | 更新日期: 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版本会给您不同的结果。事实上,没有规定同一结构体的两个实例必须具有相同的大小。