StructLayout
属性在 C# 中主要用于定义结构体或类在内存中的布局方式,这对于与非托管代码(例如通过 P/Invoke 调用的 Win32 API)进行交互时非常重要。StructLayout
属性位于 System.Runtime.InteropServices
命名空间下。使用此属性可以确保数据结构在内存中按照预期的方式组织,以便正确地传递给非托管代码。
StructLayout
主要参数
-
LayoutKind:定义成员如何排列。
Sequential
:按顺序排列字段,但会根据平台和类型的默认对齐规则自动插入填充字节。Explicit
:允许为每个字段指定确切的偏移量,通常用于精确控制内存布局。Auto
:由编译器决定最佳布局方式。
-
Pack:设置结构体成员间的填充字节数,以覆盖默认对齐。值为 1 到 32 的整数,0 表示使用默认对齐。
-
Size:指定整个结构体占用的总字节数。这在需要匹配特定大小的外部结构体时很有用。
-
CharSet :指定字符串成员使用的字符集。选项包括
Ansi
、Unicode
和Auto
。影响字符串序列化的方式。
StructLayoutAttribute 参数说明
-
LayoutKind:指定结构体成员的布局方式。
Sequential
:按照成员声明的顺序将它们放置在内存中。每个成员都会根据其类型和平台的默认对齐规则来分配内存。Explicit
:允许为每个字段显式地指定偏移量。Auto
:由编译器决定最有效的布局方式。
-
Pack :设置结构体成员间的填充字节。这可以用来覆盖默认的对齐规则。例如,如果
Pack = 1
,则不会插入任何填充字节;如果Pack = 2
,则确保每个成员从偶数地址开始等。当Pack = 0
时,表示使用默认的对齐规则,这通常意味着根据数据类型的大小来对齐(如int通常是4字节对齐)。注意,不同的平台可能有不同的默认对齐行为。
其他参数
- Size:指定整个结构体占用的总字节数。这在某些情况下很有用,比如当知道一个外部定义的结构体的确切大小,并希望C#结构体匹配这个大小时。
- CharSet :指定字符串成员使用的字符集。选项有
Ansi
、Unicode
和Auto
。这对于包含字符串或字符数组的结构体尤其重要,因为它会影响这些成员如何被序列化。 - ValueSize:当 LayoutKind 为 Explicit 时使用,它指定了特定字段所占的字节数。
使用示例
在这个例子中,MyStruct
的成员会紧密排列,中间不会有额外的填充字节。因此,尽管通常情况下 int
类型会在 byte
后面留出3个字节的空隙以保持4字节边界对齐,但这里由于设置了 Pack = 1
,所以整个结构体只占用7个字节 (1 + 4 + 2) 而不是常规情况下的8个字节。
cs
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MyStruct
{
public byte b1; // 1 byte
public int i1; // 通常4 bytes,但由于Pack=1,这里没有额外的填充
public short s1; // 2 bytes
}
class Program
{
static void Main()
{
Console.WriteLine(Marshal.SizeOf<MyStruct>()); // 输出实际大小
}
}
请注意,在调整打包值时要小心,因为错误的设置可能导致性能下降或与预期的非托管代码不兼容。
顺序布局 (Sequential)
例子中,MyStruct
的所有字段都紧密排列,没有额外的填充字节,因此整个结构体占用 7 字节。
cs
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MyStruct
{
public byte b; // 1 byte
public int i; // 4 bytes
public short s; // 2 bytes
}
class Program
{
static void Main()
{
Console.WriteLine(Marshal.SizeOf<MyStruct>()); // 输出: 7 字节
}
}
显式布局 (Explicit)
这里使用了 Explicit
布局,并且指定了每个字段的确切位置。注意,即使 Pack=1
,显式布局时我们依然能够手动控制每个字段的位置。此外,Size
参数设定了整个结构体的大小为 16 字节,这样在某些情况下可以保证与其他系统兼容。
cs
[StructLayout(LayoutKind.Explicit, Size = 16, Pack = 1)]
public struct ExplicitStruct
{
[FieldOffset(0)] public int X; // 占用前 4 个字节
[FieldOffset(4)] public int Y; // 占用接下来的 4 个字节
[FieldOffset(8)] public short Z; // 占用接下来的 2 个字节
[FieldOffset(14)] public byte W; // 占用最后一个字节
}
class Program
{
static void Main()
{
var es = new ExplicitStruct { X = 1, Y = 2, Z = 3, W = 4 };
Console.WriteLine(es.X); // 输出: 1
Console.WriteLine(es.Y); // 输出: 2
Console.WriteLine(es.Z); // 输出: 3
Console.WriteLine(es.W); // 输出: 4
}
}
注意事项
- 使用
StructLayout
时必须小心,因为错误的配置可能导致性能问题或不正确的数据交换。 - 对于跨平台的应用程序,请考虑不同操作系统可能有不同的默认对齐规则。
- 在处理显式布局时,确保所有的字段都没有重叠,否则会导致未定义行为。
- 当与非托管代码交互时,务必查阅相关文档来确定正确的布局配置。