P/Invoke各种总结(七、C#与C++结构体交互时的字节对齐问题)

在上一篇文章 C#与C++结构体的交互中介绍了如何进行结构体的交互。这里还存在的一种情况就是使用了字节对齐。

 

字节对齐

需要字节对齐的原因是因为各个硬件平台对存储空间的处理上有很大的不同。

这里简单的举个例子:假设处理器总是从存储器中读取4个字节,如果我们能保证将所有的数据类型的地址对齐成4的倍数,那么就可以用一个存储器操作来读或者写值了。否则,可能需要执行两次存储器访问,就降低了速度。

 

字节对齐的基本概念:

1、数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float类型,其自身对齐值为4,对于double型,其自身对齐值为8,单位字节。

2、结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。

3、指定对齐值:#pragma pack (value)时的指定对齐值value。

1 #pragma pack(value)
2 struct xx
3 {
4 
5 };

4、数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

 

假设有如下结构体:

1 struct struct_1
2 {
3     char a;
4     int b;
5     short c;
6     long long d;
7 };

在Windows平台的Visual Studio 2019编译器未指定字节对齐的情况下,它们的偏移信息如下:

 

指定为2字节对齐:

1 #pragma pack(2)
2 struct struct_2
3 {
4     char a;
5     int b;
6     short c;
7     long long d;
8 };

 

在C#中进行字节对齐

在C#中进行字节对齐,依旧是用到StructLayoutAttribute特性,它有一个Pack字段,可以用于指定字节对齐。

如上面的指定了字节对齐的结构体在C#中对应为:

1    [StructLayout(layoutKind:LayoutKind.Sequential,Pack = 2)]
2     struct struct_pack
3     {
4         public char a;
5         public int b;
6         public short c;
7         public long d;
8     };

 

下面使用程序来测试一下调用

创建一个C++ 动态链接库工程,命名为DataAlignmentLib。添加source.cpp文件,输入以下代码:

这里我们创建一个导出函数Test,返回一个使用了字节对齐的结构体。

 1 #pragma pack(2)
 2 struct struct_2
 3 {
 4     char a;
 5     int b;
 6     short c;
 7     long long d;
 8 };
 9 
10 extern "C" __declspec(dllexport) struct_2 Test();
11 
12 extern "C" __declspec(dllexport) struct_2 Test()
13 {
14     struct_2 ss{};
15     
16     ss.a = 'a';
17     ss.b = 2;
18     ss.c = 3;
19     ss.d = 4;
20 
21     return ss;
22 }

 

然后创建一个C#控制台程序,命名为DataAlignmentTest,将DataAlignmentLib的输出路径修改为DataAlignmentTest的Debug目录。

输入以下代码:

这里我们调用C++的导出函数Test,得到System.IntPtr类型,再将IntPtr转换为结构体。

两个工程统一使用x64平台

 1  class Program
 2     {
 3         [DllImport("DataAlignmentLib.dll")]
 4         public static extern IntPtr Test();
 5 
 6         static void Main(string[] args)
 7         {
 8             struct_normal struct_Normal = new struct_normal();
 9             struct_pack struct_Pack = new struct_pack();
10 
11             Console.WriteLine(Marshal.SizeOf(struct_Normal));
12             Console.WriteLine(Marshal.SizeOf(struct_Pack));
13 
14             IntPtr intPtr = Test();
15             struct_Pack = (struct_pack)Marshal.PtrToStructure(intPtr, typeof(struct_pack));
16 
17             Console.WriteLine(struct_Pack.a);
18             Console.WriteLine(struct_Pack.b);
19             Console.WriteLine(struct_Pack.c);
20             Console.WriteLine(struct_Pack.d);
21 
22             //未进行字节对齐的情况,程序可以执行,但内存里的值对应不起来
23             struct_Normal = (struct_normal)Marshal.PtrToStructure(intPtr, typeof(struct_normal));
24             Console.WriteLine(struct_Normal.a);
25             Console.WriteLine(struct_Normal.b);
26             Console.WriteLine(struct_Normal.c);
27             Console.WriteLine(struct_Normal.d);
28         }
29     }
30 
31 
32     [StructLayout(layoutKind:LayoutKind.Sequential,Pack = 2)]
33     struct struct_pack
34     {
35         public char a;
36         public int b;
37         public short c;
38         public long d;
39     };
40 
41     struct struct_normal
42     {
43         public char a;
44         public int b;
45         public short c;
46         public long d;
47     };

 

程序输出如下:

可以看到struct_pack使用了字节对齐,结构体大小是16,结果正常。而struct_normal未使用字节对齐,结构体大小是24,结果异常。

 

总结 

在进行平台调用时,需要注意在C++模块里,是否使用了字节对齐。如果使用了字节对齐,C#这边也要去做这样一个对应,否则就会导致程序运行不正常。

 

示例代码

posted @ 2020-11-06 15:16  zhaotianff  阅读(705)  评论(0编辑  收藏  举报