【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 时必须小心,因为错误的配置可能导致性能问题或不正确的数据交换。
  • 对于跨平台的应用程序,请考虑不同操作系统可能有不同的默认对齐规则。
  • 在处理显式布局时,确保所有的字段都没有重叠,否则会导致未定义行为。
  • 当与非托管代码交互时,务必查阅相关文档来确定正确的布局配置。
相关推荐
Java Fans2 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手2 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
码农君莫笑2 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
鲤籽鲲3 小时前
C# Random 随机数 全面解析
android·java·c#
fkdw6 小时前
C# Newtonsoft.Json 反序列化派生类数据丢失问题
c#·json
浅尝辄止;8 小时前
C# 异步编程
c#
ou.cs12 小时前
c# 实现一个简单的异常日志记录(异常迭代+分片+定时清理)+AOP Rougamo全局注入
c#
一只小小汤圆14 小时前
编译笔记:vs 中 正在从以下位置***加载符号 C# 中捕获C/C++抛出的异常
c++·c#