【C#】StructLayout的使用

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 时必须小心,因为错误的配置可能导致性能问题或不正确的数据交换。
  • 对于跨平台的应用程序,请考虑不同操作系统可能有不同的默认对齐规则。
  • 在处理显式布局时,确保所有的字段都没有重叠,否则会导致未定义行为。
  • 当与非托管代码交互时,务必查阅相关文档来确定正确的布局配置。
相关推荐
lzhdim4 小时前
C#不为人知的10个魔法特性:资深开发者也会震惊的底层奥秘
java·前端·javascript·算法·c#
前端 贾公子7 小时前
LangChain核心组件 == 提示词(Prompts)
开发语言·c#
码艺-Alimjan9 小时前
Web 网站打包桌面应用的另一种方式,超级简单(C# exe 33Kb)
开发语言·前端·c#
海天鹰10 小时前
C#入门:显示窗口
c#
淡海水10 小时前
16-02-C#常用数据结构源码-附录B-源码索引速查
unity·c#·游戏引擎·il2cpp
海天鹰11 小时前
C#入门:在cmd显示字符
c#
波力海苔夹心脆6751 天前
C# 序列化与反序列化详解:System.Text.Json、Newtonsoft.Json、XmlSerializer 用法、特性选项与安全实践
经验分享·后端·c#·json·.net
小羊没烦恼!1 天前
在Scrum中实施敏捷建模
java·开发语言·windows·算法·c#
Lost of 程序猿1 天前
建造者模式实战:告别“十参数构造函数“的数据导出任务
后端·设计模式·c#·asp.net
mudtools1 天前
在.NET现有系统中快速集成飞书任务分配能力
后端·c#·.net