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 }
執行結果: