1. 程式人生 > 實用技巧 >P/Invoke各種總結(八、在C#中使用Union聯合體)

P/Invoke各種總結(八、在C#中使用Union聯合體)

Union聯合體(共用體)

這裡稍微簡單介紹一下Union這種型別。

有時候需要使幾種不同型別的變數存放到同一段記憶體單元中,例如:可把一個整型變數、一個字串變數放在同一個地址開始的記憶體單元中。以上兩個變數在記憶體中佔用的位元組數不同,但都從同一個地址開始存放,也就是使用覆蓋技術,幾個變數互相覆蓋。這種使用幾個不同的變數共同佔用同一段記憶體的結構,稱為Union。

1 union
2     {        
3         int a;
4         char b;
5     };

union的特點是:

1、同一個記憶體段可以用來存放幾種不同型別的成員,但在同一時刻只能存放其中一種。

2、union起作用的成員是最後一次存放的變數,在存入一個新的成員後原有的成員就失去作用。

union和struct的區別

struct:所佔記憶體長度是各成員佔的記憶體之和,每個成員分別佔用某個自己的記憶體單元

union:所佔記憶體長度等於最長的成員的長度

在C#中使用union

在C#中與union互動,需要用到StructLayoutAttribute特性和FieldOffsetAttribute特性,關於這兩個特性的介紹,可以參考https://www.cnblogs.com/zhaotianff/p/12510286.html

其實在知道union的原理後,在C#中進行對映也是非常簡單。

如前面示例程式碼中的union,在C#中的表示形式如下:

1    [StructLayout(LayoutKind.Explicit)]
2     struct MyStruct
3     {
4         [FieldOffset(0)]int a;
5         [FieldOffset(0)]char b;
6    };

在上一篇文章中,介紹了StructLayoutAttribute類的Pack欄位,這裡再介紹一下另外一個屬性,StructLayoutAttribute.Size欄位,Size欄位用於指示類或結構的絕對大小

在使用union時,如果不是C#中的值型別,就需要指定大小。

測試程式:

這是最簡單的情況

C++

 1 union MYUNION
 2 {
 3     int b;
 4     double d;
 5 };
 6 
 7 extern "C" __declspec(dllexport) MYUNION GetMyUnion();
 8 
 9 extern "C" __declspec(dllexport) MYUNION GetMyUnion()
10 {
11     MYUNION  myunion;    
12     myunion.b = 10;
13     return myunion;
14 }

C#

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Runtime.InteropServices;
 7 
 8 namespace UnionTest
 9 {
10     class Program
11     {
12         [DllImport("UnionLib.dll")]
13         public static extern MyUnion GetMyUnion();
14 
15         static void Main(string[] args)
16         {
17             
18             //只有值型別的情況
19             var myunion = GetMyUnion();
20             Console.WriteLine(myunion.b);        
21         }
22     }
23 }

執行結果:

帶引用型別的情況:

需要注意的是:值型別和引用型別不允許重疊。所以在單獨使用帶引用型別的union時,需要分開處理。

這裡定義了兩個匯出函式,TestUnion2函式可以傳入MYUNION2,並進行輸出。GetMyUnion2函式直接返回MYUNION2

C++

 1 union MYUNION2
 2 {
 3     int i;
 4     char str[128];
 5 };
 6 
 7 extern "C" __declspec(dllexport) void TestUnion2(MYUNION2 u, int type);
 8 
 9 extern "C" __declspec(dllexport) void TestUnion2(MYUNION2 u, int type)
10 {
11     if (type == 1)
12     {
13         std::cout << u.i << std::endl;
14     }
15     else
16     {
17         std::cout << u.str << std::endl;
18     }
19 }
20 
21 extern "C" __declspec(dllexport) MYUNION2 GetMyUnion2();
22 
23 extern "C" __declspec(dllexport) MYUNION2 GetMyUnion2()
24 {
25     MYUNION2  myunion2;
26     strcpy_s(myunion2.str, 11, "HelloWorld");
27     return myunion2;
28 }

C#

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Runtime.InteropServices;
 7 
 8 namespace UnionTest
 9 {
10     class Program
11     {
12         [DllImport("UnionLib.dll")]
13         public static extern IntPtr GetMyUnion2();
14 
15 
16         [DllImport("UnionLib.dll")]
17         public static extern void TestUnion2(MyUnion2_INT mm, int i);
18 
19         [DllImport("UnionLib.dll")]
20         public static extern void TestUnion2(MyUnion2_STR mm,int i);
21 
22         static void Main(string[] args)
23         {          
24             //使用了引用型別無法直接通過簽名返回,需要進行轉換
25             //如果這裡是使用了MYUNION2中的i欄位,可以直接使用MyUnion2_INT作為返回值
26             var myunion2Ptr = GetMyUnion2();
27             MyUnion2_STR unionStr = new MyUnion2_STR();
28             unionStr = (MyUnion2_STR)Marshal.PtrToStructure(myunion2Ptr, typeof(MyUnion2_STR));
29             Console.WriteLine(unionStr.str);
30 
31             //注意:值型別和引用型別不允許重疊
32             //在使用引用型別的情況下,無法直接返回union
33             MyUnion2_INT mu1 = new MyUnion2_INT();
34             mu1.i = 30;
35             TestUnion2(mu1, 1);
36 
37             MyUnion2_STR mu2 = new MyUnion2_STR();
38             mu2.str = "abc";
39             TestUnion2(mu2, 2);
40         }
41     }
42 
43 
44 
45     /// <summary>
46     /// 如果使用了union中的int欄位,就使用這個結構體
47     /// </summary>
48     [StructLayout(LayoutKind.Explicit, Size = 128)]
49     public struct MyUnion2_INT
50     {
51         [FieldOffset(0)]
52         public int i;
53     }
54 
55     /// <summary>
56     /// 如果使用了union中的char[]欄位,就使用這個結構體
57     /// </summary>
58     [StructLayout(LayoutKind.Sequential)]
59     public struct MyUnion2_STR
60     {
61         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
62         public string str;
63     }
64 
65 }

執行結果:

最看再看一下在struct中使用union的情況:

這段示例程式碼中使用了Windows Data Types(LPWSTR),在不同的平臺中,指標的大小不一樣,所以在C#中呼叫時,需要根據平臺,定義對應的結構體。

C++

 1 struct MYSTRUCTUNION
 2 {
 3     UINT uType;
 4     union
 5     {
 6         LPWSTR pStr;
 7         char cStr[260];
 8     };
 9 };
10 
11 extern "C" __declspec(dllexport) MYSTRUCTUNION GetMyUnion3();
12 
13 extern "C" __declspec(dllexport) MYSTRUCTUNION GetMyUnion3()
14 {
15     MYSTRUCTUNION myunion;
16     myunion.uType = 0;
17     myunion.pStr = L"HelloWorld";
18     return myunion;
19 }

C#

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Runtime.InteropServices;
 7 
 8 namespace UnionTest
 9 {
10     class Program
11     {
12         [DllImport("UnionLib.dll")]
13         public static extern MyStructUnion GetMyUnion3();
14 
15         static void Main(string[] args)
16         {          
17             //結構體和聯合體一起使用的情況
18             //引用型別部分使用指標再進行轉換,直接用字串將會封送失敗
19             var myStructUnion = GetMyUnion3();
20             var str = Marshal.PtrToStringUni(myStructUnion.pStr);
21             Console.WriteLine(str);
22         }
23     }
24 
25 #if x86
26     /// <summary>
27     /// 32位
28     /// </summary>
29     [StructLayout(LayoutKind.Explicit, Size = 264)]
30     public struct MyStructUnion
31     {
32         [FieldOffset(0)]
33         public uint uType;
34 
35         [FieldOffset(4)]
36         public IntPtr pStr;
37 
38         [FieldOffset(4)]
39         public IntPtr cStr;
40     }
41 
42 #endif
43 
44 
45 #if x64
46     /// <summary>
47     /// 64位
48     /// </summary>
49     [StructLayout(LayoutKind.Explicit, Size = 272)]
50     public struct MyStructUnion
51     {
52         [FieldOffset(0)]
53         public uint uType;
54 
55         [FieldOffset(8)]
56         public IntPtr pStr;
57 
58         [FieldOffset(8)]
59         public IntPtr cStr;
60     }
61 #endif
62 
63 }

執行結果: