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

Why cannot marshal struct with auto layout

我在编组具有自动布局类型的结构时遇到了一个奇怪的行为。

举个例子:举个简单的代码:

[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: Type 'StructAutoLayout' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.

所以这意味着编译器在编译时不知道结构大小?我确定此属性会重新排序结构字段然后对其进行编译,但事实并非如此。

没有任何意义。编组用于互操作 - 在进行互操作时,双方必须就 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;
}

处理 native/unmanaged 代码时,几乎不涉及任何元数据 - 仅涉及指针和值。另一方无法知道该结构的实际布局方式,它期望你们事先商定的固定布局。

.NET 倾向于让您被宠坏 - 几乎所有东西 都能正常工作 。当与 C++ 之类的东西进行互操作时,情况并非如此——如果你只是猜测,你很可能最终会得到一个通常有效的解决方案,但偶尔会导致整个应用程序崩溃。在使用非托管/本机代码执行任何操作时,请确保您完全理解自己在做什么 - 非托管互操作在这种情况下非常脆弱。

现在,Marshal class 专门 设计用于非托管互操作。如果您阅读 Marshal.SizeOf 的文档,它明确表示

Returns the size of an unmanaged type in bytes.

当然,

You can use this method when you do not have a structure. The layout must be sequential or explicit.

The size returned is the size of the unmanaged type. The unmanaged and managed sizes of an object can differ. For character types, the size is affected by the CharSet value applied to that class.

如果类型 不能 可能被编组,那么 Marshal.SizeOf return 应该怎样?这甚至没有意义:)

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

如果您改为使用具有显式(或顺序)布局的 struct,您将对非托管类型的布局方式有明确的约定,并且 Marshal.SizeOf 将起作用。但是,即使那样,它也只会 return 非托管类型的大小,而不是托管类型的大小 - 仍然可以不同。同样,两者在不同系统上可能不同(例如,IntPtr 在 32 位系统上是四个字节,在 64 位系统上是八个字节,当 运行ning 作为 64 位系统时申请)。

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

如果您需要分析托管内存,请使用内存分析器(如 CLRProfiler)。但是请理解,您仍在非常特定的环境中分析内存——不同的系统或 .NET 版本可能会给您不同的结果。事实上,并没有说相同结构的两个实例必须具有相同的大小。