【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 :指定字符串成员使用的字符集。选项有 AnsiUnicodeAuto。这对于包含字符串或字符数组的结构体尤其重要,因为它会影响这些成员如何被序列化。
  • 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 时必须小心,因为错误的配置可能导致性能问题或不正确的数据交换。
  • 对于跨平台的应用程序,请考虑不同操作系统可能有不同的默认对齐规则。
  • 在处理显式布局时,确保所有的字段都没有重叠,否则会导致未定义行为。
  • 当与非托管代码交互时,务必查阅相关文档来确定正确的布局配置。
相关推荐
CuPhoenix2 小时前
【沧海拾昧】C# .NET8 WinForms程序在主显示器125%/150%缩放下尺寸显示异常的解决办法
c#·.net·winforms
wangnaisheng2 小时前
【C#】垃圾回收
c#
你拿什么来感同身受3 小时前
WinForm程序嵌入Web网页
c#
__water4 小时前
『功能项目』QFrameWorkBug拖拽功能【66】
c#·unity引擎·拖拽功能
AutoAutoJack5 小时前
C# 委托(Delegate)二
开发语言·数据结构·算法·架构·c#
华山自控编程5 小时前
C#中的排除法解决问题
c#
小码编匠7 小时前
C# 实现国产 Linux(银河麒麟、UOS)桌面录制并保存为 MP4
linux·后端·c#
Artistation Game8 小时前
二、鼠标的解锁与锁定
unity·c#·游戏引擎
妙妙屋(zy)11 小时前
弹幕树洞项目功能新增篇
c#·.net