c# - 为什么不能使用自动布局编码结构

标签 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>());
}

它抛出一个异常:

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 /非托管代码时,几乎不涉及任何元数据——只有指针和值。另一方无法知道结构的实际布局方式,它期望你们双方事先同意的固定布局。

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

现在,Marshal 类是为非托管互操作专门设计的。如果您阅读了 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 应该返回什么?这甚至没有意义:)

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

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

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

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

关于c# - 为什么不能使用自动布局编码结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31857624/

相关文章:

C#:如何导出到 Excel 电子表格?使用 XSLT/LINQ/其他方法?

java - 将位图转换为圆形位图的最佳方法是什么

c# - 阻止进程子窗口?

c++ - 为类的实例提供指向结构的指针

c# - 如何将 JavaScript 日期对象转换为刻度

c# - 如何在 Excel VBA 中使用 c# 类?

c# - 在文本框中从右到左输入?

c# - 等待阻塞集合(队列)在 C# 中减小大小

c - 应为 const char * 但参数是 char 类型

c - C中指针丢失值