自学内容网 自学内容网

【C#】StructLayout的使用

StructLayout 属性在 C# 中主要用于定义结构体或类在内存中的布局方式,这对于与非托管代码(例如通过 P/Invoke 调用的 Win32 API)进行交互时非常重要。StructLayout 属性位于 System.Runtime.InteropServices 命名空间下。使用此属性可以确保数据结构在内存中按照预期的方式组织,以便正确地传递给非托管代码。

StructLayout 主要参数

  • LayoutKind:定义成员如何排列。

    • Sequential:按顺序排列字段,但会根据平台和类型的默认对齐规则自动插入填充字节。
    • Explicit:允许为每个字段指定确切的偏移量,通常用于精确控制内存布局。
    • Auto:由编译器决定最佳布局方式。
  • Pack:设置结构体成员间的填充字节数,以覆盖默认对齐。值为 1 到 32 的整数,0 表示使用默认对齐。

  • Size:指定整个结构体占用的总字节数。这在需要匹配特定大小的外部结构体时很有用。

  • CharSet:指定字符串成员使用的字符集。选项包括 AnsiUnicodeAuto。影响字符串序列化的方式。

 

StructLayoutAttribute 参数说明

  • LayoutKind:指定结构体成员的布局方式。

    • Sequential:按照成员声明的顺序将它们放置在内存中。每个成员都会根据其类型和平台的默认对齐规则来分配内存。
    • Explicit:允许为每个字段显式地指定偏移量。
    • Auto:由编译器决定最有效的布局方式。
  • Pack:设置结构体成员间的填充字节。这可以用来覆盖默认的对齐规则。例如,如果 Pack = 1,则不会插入任何填充字节;如果 Pack = 2,则确保每个成员从偶数地址开始等。当 Pack = 0 时,表示使用默认的对齐规则,这通常意味着根据数据类型的大小来对齐(如int通常是4字节对齐)。注意,不同的平台可能有不同的默认对齐行为。

其他参数

  • Size:指定整个结构体占用的总字节数。这在某些情况下很有用,比如当知道一个外部定义的结构体的确切大小,并希望C#结构体匹配这个大小时。
  • CharSet:指定字符串成员使用的字符集。选项有 AnsiUnicode 和 Auto。这对于包含字符串或字符数组的结构体尤其重要,因为它会影响这些成员如何被序列化。
  • ValueSize:当 LayoutKind 为 Explicit 时使用,它指定了特定字段所占的字节数。

使用示例

在这个例子中,MyStruct 的成员会紧密排列,中间不会有额外的填充字节。因此,尽管通常情况下 int 类型会在 byte 后面留出3个字节的空隙以保持4字节边界对齐,但这里由于设置了 Pack = 1,所以整个结构体只占用7个字节 (1 + 4 + 2) 而不是常规情况下的8个字节。

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 字节。

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 字节,这样在某些情况下可以保证与其他系统兼容。

[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 时必须小心,因为错误的配置可能导致性能问题或不正确的数据交换。
  • 对于跨平台的应用程序,请考虑不同操作系统可能有不同的默认对齐规则。
  • 在处理显式布局时,确保所有的字段都没有重叠,否则会导致未定义行为。
  • 当与非托管代码交互时,务必查阅相关文档来确定正确的布局配置。

 

 


原文地址:https://blog.csdn.net/wangnaisheng/article/details/142461027

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!