StructLayout特性允许我们控制Structure语句块的元素在内存中的排列方式,以及当这些元素被传递给外部DLL时,运行库排列这些元素的方式。

1.Sequential,顺序布局

[StructLayout(LayoutKind.Sequential, Pack = 1)] 
struct Prop
{
public int a;
public int b;
}

这样在内存里是先排a,再排b,也就是如果能取到a的地址和b的地址,则相差一个int类型的长度,4字节。因为默认的内存排列就是Sequential,也就是按成员的先后顺序排列。

2.Explicit,精确布局

需要用FieldOffset()设置每个成员的位置

[StructLayout(LayoutKind.Explicit)] 
struct Prop2
{
[FieldOffset(0)]
public int a;
[FieldOffset(4)]
public int b;
}

StructLayout特性支持三种附加字段:CharSet、Pack、Size。 

  • CharSet定义在结构体中的字符串成员在结构被传给DLL时的排列方式。可以是Unicode、Ansi或Auto。 
  • Pack定义了结构体的封装大小。可以是1、2、4、8、16、32、64、128或特殊值0。特殊值0表示当前操作平台默认的压缩大小。比如,上面的顺序布局中,就设置结构大小为1。
  • Size指示类或结构的绝对大小,此字段必须为等于或大于总大小。

3.一般在实际开发中,我们的属性列表都是有很多条组成的,所以需要在结构体中嵌入数组。此处,用的上文的Prop结构体作为数组的类型。

struct Props
{
[MarshalAs(UnmangedType.ByValArray, SizeConst = 3)]
public Prop[] data;
}

这里用到了用到MashalAs特性,它用于描述字段、方法或参数的封送处理格式。用它作为参数前缀并指定目标需要的数据类型。

嵌入结构体的数组可以通过使用UnmanagedType.ByValArray并用MarshalAsAttribute.SizeConst指定数组大小的 MarshalAs 属性来封送。包含字符串的数组可以使用UnmanagedType.ByValTStr 来指定字符串。

定义了结构,如何使用呢?需要使用Marshal.PtrTostructure将数据从非托管内存块封送到新分配的指定类型的托管对象。

unsafe
{
// 一般从服务器获得属性的值prop
byte[] prop;
if (prop != null && prop.Length == 8)
{
fixed (byte* pb = &prop[0])
{
// 通过PtrTostructure将数据从非托管内存块封送到新分配的指定类型的托管对象。
// 参数:
// 第一个参数ptr:
// 指向非托管内存块的指针。
//
// 第二个参数structureType:
// 要创建的对象的类型。 此对象必须表示格式化类或结构。
//
// 返回结果:
// 指向一个包含数据的托管的对象 ptr 参数。
Props p = (Props)Marshal.PtrToStructure(new IntPtr(pb), typeof(Props));
}
}
}