一、用StructLayout特性限定声明结构或类
公共语言运行库利用StructLayoutAttribute控制类或结构的数据字段在托 管内存中的物理布局,即类或结构需要按某种方式排列。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。它的构造函数中用LayoutKind值初始化 StructLayoutAttribute 类的新实例。 LayoutKind.Sequential 用于强制将成 员按其出现的顺序进行顺序布局。
LayoutKind.Explicit 用于控制每个数据成员的精确位置。利用 Explicit, 每个成员必须使用 FieldOffsetAttribute 指示此字段在类型中的位置。如:
- [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
- public class MySystemTime
- {
- [FieldOffset(0)]public ushort wYear;
- [FieldOffset(2)]public ushort wMonth;
- [FieldOffset(4)]public ushort wDayOfWeek;
- [FieldOffset(6)]public ushort wDay;
- [FieldOffset(8)]public ushort wHour;
- [FieldOffset(10)]public ushort wMinute;
- [FieldOffset(12)]public ushort wSecond;
- [FieldOffset(14)]public ushort wMilliseconds;
- }
下面是针对API中OSVERSIONINFO结构,在.net中定义对应类或结构的例子:
/**********************************************
* API中定义原结构声明
* OSVERSIONINFOA STRUCT
* dwOSVersionInfoSize DWORD ?
* dwMajorVersion DWORD ?
* dwMinorVersion DWORD ?
* dwBuildNumber DWORD ?
* dwPlatformId DWORD ?
* szCSDVersion BYTE 128 dup (?)
* OSVERSIONINFOA ENDS
*
* OSVERSIONINFO equ <OSVERSIONINFOA>
*********************************************/
//.net中声明为类
- [ StructLayout( LayoutKind.Sequential )]
- public class OSVersionInfo
- {
- public int OSVersionInfoSize;
- public int majorVersion;
- public int minorVersion;
- public int buildNumber;
- public int platformId;
- [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]
- public String versionString;
- }
//或者
//.net中声明为结构
- [ StructLayout( LayoutKind.Sequential )]
- public struct OSVersionInfo2
- {
- public int OSVersionInfoSize;
- public int majorVersion;
- public int minorVersion;
- public int buildNumber;
- public int platformId;
- [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]
- public String versionString;
- }
此例中用到MashalAs特性,它用于描述字段、方法或参数的封送处理格式。用它作为参数前缀并指定目标需要的数据类型。例如,以下代码将两个参数作为数据类型长指针封送给 Windows API 函数的字符串 (LPStr):
[MarshalAs(UnmanagedType.LPStr)]
String existingfile;
[MarshalAs(UnmanagedType.LPStr)]
String newfile;
注意结构作为参数时候,一般前面要加上ref修饰符,否则会出现错误:对象的引用没有指定对象的实例。
- [ DllImport( "kernel32", EntryPoint="GetVersionEx" )]
- public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi );
二、StructLayout特性
公共语言运行库利用StructLayoutAttribute控制类或结构的数据字段在托管内存中的物理布局,即类或结构需要按某种方式排列。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。它的构造函数中用 LayoutKind值初始化 StructLayoutAttribute 类的新实例。 LayoutKind.Sequential 用于强制将成员按其出现的顺序进行顺序布局。
StructLayout特性允许我们控制Structure语句块的元素在内存中的排列方式,以及当这些元素被传递给外部DLL时,运行库排列这些元素的方式。Visual Basic结构的成员在内存中的顺序是按照它们出现在源代码中的顺序排列的,尽管编译器可以自由的插入填充字节来安排这些成员,以便使得16位数值用子边界对齐,32位数值用双字边界对齐。
使用这种排列(未压缩布局)提供的性能最佳。
在Visual Basic 6的用户自定义结构是未压缩的,而且我们不可以改变这一默认设置。在VB.NET中可以改变这种设置,并且可以通过System.Runtime.InteropServices.StructLayout 特性精确的控制每一个结构成员的位置。
System.Runtime.InteropServices.StructLayout 允许的值有StructLayout.Auto StructLayout.Sequential StructLayout.Explicit.
1.Sequential,顺序布局,比如
struct S1
{
int a;
int b;
}
那么默认情况下在内存里是先排a,再排b
也就是如果能取到a的地址,和b的地址,则相差一个int类型的长度,4字节
[StructLayout(LayoutKind.Sequential)]
struct S1
{
int a;
int b;
}
这样和上一个是一样的.因为默认的内存排列就是Sequential,也就是按成员的先后顺序排列.
2.Explicit,精确布局
需要用FieldOffset()设置每个成员的位置
这样就可以实现类似c的公用体的功能
[StructLayout(LayoutKind.Explicit)]
struct S1
{
[FieldOffset(0)]
int a;
[FieldOffset(0)]
int b;
}
这样a和b在内存中地址相同
StructLayout特性支持三种附加字段:CharSet、Pack、Size。
· CharSet定义在结构中的字符串成员在结构被传给DLL时的排列方式。可以是Unicode、Ansi或Auto。
默认为Auto,在WIN NT/2000/XP中表示字符串按照Unicode字符串进行排列,在WIN 95/98/Me中则表示按照ANSI字符串进行排列。
· Pack定义了结构的封装大小。可以是1、2、4、8、16、32、64、128或特殊值0。特殊值0表示当前操作平台默认的压缩大小。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct LIST_OPEN
{
public int dwServerId;
public int dwListId;
public System.UInt16 wRecordSize;
public System.UInt16 wDummy;
public int dwFileSize;
public int dwTotalRecs;
public NS_PREFETCHLIST sPrefetch;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
public string szSrcMach;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
public string szSrcComp;
}
此例中用到MashalAs特性,它用于描述字段、方法或参数的封送处理格式。用它作为参数前缀并指定目标需要的数据类型。
例如,以下代码将两个参数作为数据类型长指针封送给 Windows API 函数的字符串 (LPStr):
[MarshalAs(UnmanagedType.LPStr)]
String existingfile;
[MarshalAs(UnmanagedType.LPStr)]
String newfile;
注意结构作为参数时候,一般前面要加上ref修饰符,否则会出现错误:对象的引用没有指定对象的实例。
[ DllImport( "kernel32", EntryPoint="GetVersionEx" )]
public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi );
在使用结构体指针,进行C#和C++的互相调用。边界对齐是一个大问题,因为边界对齐问题,结构体的成员并不是顺序在内存一个挨着一个的排序。
而且在C++中可以使用#pragma pack(n)改变边界对齐的方案,那C#的结构体怎么对应C++的结构体那?(什么是边界对齐,这里不解释,
不懂得可以去看看C++基本编程之类的书好好恶补一下.)
第一:最普通的情况下,C++代码没有使用#pragma pack(n)改变边界对齐,这里C#可以使用两种方法处理,LayoutKind.Explicit 和
LayoutKind.Sequential,建议使用后者,虽然前者是万金油,不过使用起来太累有爱出错。
C++:
复制代码
1 struct Test1
2 {
3 int test1;
4 char test2;
5 __int64 test3;
6 short test4;
7 };
8
9 Test1 * __stdcall GetTest1()
10 {
11 test1.test1 = 10;
12 test1.test2 = 11;
13 test1.test3 = 12;
14 test1.test4 = 13;
15 return &test1;
16 }
复制代码
C#:(这里有两种方案,使用LayoutKind.Explicit 和LayoutKind.Sequential,注意一下)
复制代码
1 [StructLayout(LayoutKind.Explicit)]
2 public struct Test
3 {
4 [FieldOffset(0)]
5 public int test1;
6 [FieldOffset(4)]
7 public char test2;
8 [FieldOffset(8)]
9 public Int64 test3;
10 [FieldOffset(16)]
11 public short test4;
12 }
13
14 [StructLayout(LayoutKind.Sequential)]
15 public struct Test1
16 {
17 public int test1;
18 public char test2;
19 public Int64 test3;
20 public short test4;
21 }
22
23 [DllImport("TestDll")]
24 public static extern IntPtr GetTest1();
25
26 //#################################
27 IntPtr p = GetTest1();
28 Test test = (Test)Marshal.PtrToStructure(p, typeof(Test));
29 Console.WriteLine(test.test1 + test.test2 + test.test3 + test.test4);
30
31 IntPtr p1 = GetTest1(); //Auto pack
32 Test1 test1 = (Test1)Marshal.PtrToStructure(p1, typeof(Test1));
33 Console.WriteLine(test1.test1 + test1.test2 + test1.test3 + test1.test4);
复制代码
第二:特殊的情况下,C++代码使用#pragma pack(n)改变了边界对齐。这里要使用C#要使用 [StructLayout(LayoutKind.Sequential, Pack = N)] 对齐,否则出错。
C++:
复制代码
1 #pragma pack(1)
2 struct Test2
3 {
4 int test1;
5 char test2;
6 __int64 test3;
7 short test4;
8 };
9 #pragma pack()
10
11 #pragma pack(2)
12 struct Test3
13 {
14 int test1;
15 char test2;
16 __int64 test3;
17 short test4;
18 };
19 #pragma pack()
20
21
22 #pragma pack(4)
23 struct Test4
24 {
25 int test1;
26 char test2;
27 __int64 test3;
28 short test4;
29 };
30 #pragma pack()
复制代码
C#:
复制代码
1 [StructLayout(LayoutKind.Sequential, Pack = 1)]
2 struct Test2
3 {
4 public int test1;
5 public char test2;
6 public Int64 test3;
7 public short test4;
8 }
9
10 [StructLayout(LayoutKind.Sequential, Pack = 2)]
11 struct Test3
12 {
13 public int test1;
14 public char test2;
15 public Int64 test3;
16 public short test4;
17 }
18
19 [StructLayout(LayoutKind.Sequential, Pack = 4)]
20 struct Test4
21 {
22 public int test1;
23 public char test2;
24 public Int64 test3;
25 public short test4;
26 }
27
28
29 [DllImport("TestDll")]
30 public static extern IntPtr GetTest2();
31
32 [DllImport("TestDll")]
33 public static extern IntPtr GetTest3();
34
35 [DllImport("TestDll")]
36 public static extern IntPtr GetTest4();
37
38 //#################################
39 IntPtr p2 = GetTest2(); //pack 1
40 Test2 test2 = (Test2)Marshal.PtrToStructure(p2, typeof(Test2));
41
42 IntPtr p3 = GetTest3(); //pack 2
43 Test3 test3 = (Test3)Marshal.PtrToStructure(p3, typeof(Test3));
44
45 IntPtr p4 = GetTest4(); //pack4
46 Test4 test4 = (Test4)Marshal.PtrToStructure(p4, typeof(Test4));
47
48 Console.WriteLine(test2.test1 + test2.test2 + test2.test3 + test2.test4);
49 Console.WriteLine(test3.test1 + test3.test2 + test3.test3 + test3.test4);
50 Console.WriteLine(test4.test1 + test4.test2 + test4.test3 + test4.test4);
复制代码
最后总结一下,LayoutKind有3个枚举值。LayoutKind.Auto ,LayoutKind.Explicit 和LayoutKind.Sequential.LayoutKind.Auto或者为使用LayoutKind属性的结构体,
进行P-INVOKE调用会抛出异常,改类型不允许进行P-INVOKE调用,LayoutKind.Sequential在内存中顺序布局,一般情况(上面两种)推荐用这个。LayoutKind.Explicit
只推荐特殊情况使用,因为他会明确指定成员的内存offset,很强大也很繁琐。