c#編程指南——平臺調用P-INVOKE完全掌握,C#和C++互相調用

c#編程指南(九) 平臺調用P-INVOKE完全掌握,C#和C++互相調用

第一:C# 調用C++,使用P-INVOKE技術調用C++編寫的動態鏈接庫。而動態鏈接庫導出的一般有兩種調用協議,__stdcall和_cdecl。下面展示調用兩種不同調用協議的方法:

C++:

1 int __stdcall Test1(int i)
2 {
3 return printf("The __stdcall invoke convesion:%d\n", i);
4 }
5
6 int _cdecl Test2(int i)
7 {
8 return printf("The _cdecl invoke convesion:%d\n", i);
9 }

複製代碼

c#:

1 [DllImport("TestDll", CallingConvention = CallingConvention.StdCall)]
2 public static extern int Test1(int i);
3
4 [DllImport("TestDll",CallingConvention= CallingConvention.Cdecl)]
5 public static extern int Test2(int i);
6
7 public void Run()
8 {
9 ...
10 Test1(20);
11 Test2(30);
12 ...
13 }

複製代碼

第二:C#調用C++代碼,也可以間接的使用C++的函數指針。(強烈建議不要使用此方法)

c++:

1 typedef int ( __stdcall * FunctionPointerType1)(int i);
2 typedef int ( __cdecl * FunctionPointerType2)(int i);
3
4 int __stdcall Test1(int i)
5 {
6 return printf("The __stdcall invoke convesion:%d\n", i);
7 }
8
9 int _cdecl Test2(int i)
10 {
11 return printf("The _cdecl invoke convesion:%d\n", i);
12 }
13
14 FunctionPointerType1 GetFunctionPointer1(int i)
15 {
16 return Test1;
17 }
18
19 FunctionPointerType2 GetFunctionPointer2(int i)
20 {
21 return Test2;
22 }

複製代碼

C#:

1 [DllImport("TestDll")]
2 public static extern FunctionPointerType GetFunctionPointer1(int i);
3
4 [DllImport("TestDll")]
5 [return:MarshalAs(UnmanagedType.FunctionPtr)]
6 public static extern FunctionPointerType GetFunctionPointer2(int i);
7
8 public void Run()
9 {
10 ...
11 FunctionPointerType func1 = GetFunctionPointer1(0);
12 FunctionPointerType func2 = GetFunctionPointer2(0);
13 //實驗證明無論__stdcall和_cdecl函數指針C#都可以正常調用,
14 //具體內部不知道微軟如何做的,高手指教了。
15 func1(40);
16 func2(50);
17 ...
18 }

複製代碼

第三:C++調用C#函數,C#定義方法和方法的委託。傳遞委託給C++函數,.NET Framework會自動把委託轉化成函數指針,對應的C++函數指針類型只能是__stdcall調用協議的,因爲C#的方法默認編程成這個調用協議的,然後使用C++函數指針調用即可。

c#:

1 public delegate int FunctionPointerType(int i);
2
3 [DllImport("TestDll")]
4 public static extern int SetFunctionPointer(FunctionPointerType pointer);
5
6 FunctionPointerType test1 = delegate(int i) { Console.WriteLine("The C# Anonymous Method :" + i); return 0; };
7
8 public int CSharpTest1(int i)
9 {
10 Console.WriteLine("The C# member Method :" + i);
11 return 0;
12 }
13
14 public static int CSharpTest2(int i)
15 {
16 Console.WriteLine("The C# class Method :" + i);
17 return 0;
18 }
19
20 public void Run()
21 {
22 ...
23 FunctionPointerType test2 = CSharpTest1;
24 FunctionPointerType test3 = CSharpTest2;
25 SetFunctionPointer(test1);
26 SetFunctionPointer(test2);
27 SetFunctionPointer(test3);
28 ...
29 }

複製代碼

c++:

1 typedef int ( __stdcall * FunctionPointerType1)(int i);
2
3 int SetFunctionPointer(FunctionPointerType1 pointer)
4 {
5 return pointer(1);
6 }

複製代碼

微軟很牛逼,基本上C#和C++互相調用完全沒有問題,只要注意參數的Marshar和調用協議就可以了。

下載:代碼

c#編程指南(十) 平臺調用P-INVOKE完全掌握, 字符串和指針

可以說新手使用P-INVOKE最開始的頭疼就是C#和C++的字符串傳遞,因爲這裏涉及到兩個問題。

第一:C#的string和C++的字符串首指針如何對應。

第二:字符串還有ANSI和UNICODE(寬字符串)之分。

本文分三部分闡述:

第一:字符串指針當輸入參數,

第二:字符串指針作爲返回值,

第三:字符串指針作爲輸入輸出參數。

C++部分的測試代碼很簡單這裏就全部貼出來了:

1 #include "stdafx.h"
2 #include "TestDll.h"
3 #include <stdio.h>
4 #include <string.h>
5 #include <tchar.h>
6
7
8 static char * _hello = "Hello,World!!";
9 static TCHAR * _helloW = TEXT("Hello,World!!");
10
11 void __stdcall PrintString(char * hello)
12 {
13 printf("%s\n",hello);
14 }
15
16 void __stdcall PrintStringW(TCHAR * hello)
17 {
18 _tprintf(TEXT("%s\n"),hello);
19 }
20
21
22 char * __stdcall GetStringReturn()
23 {
24 return _hello;
25 }
26
27 TCHAR * __stdcall GetStringReturnW()
28 {
29 return _helloW;
30 }
31
32
33 void __stdcall GetStringParam(char * outHello,int len)
34 { //output "aaaaaaaa"
35 for(int i= 0; i< len -1 ;i++) outHello[i] = 'a';
36 outHello[len - 1] = '\0';
37 }
38
39 void __stdcall GetStringParamW(TCHAR * outHello,int len)
40 { //output "aaaaaaaa" unicode version.
41 for(int i= 0; i< len -1 ;i++) outHello[i] = TEXT('a');
42 outHello[len - 1] = TEXT('\0');
43 }

複製代碼

下面看C#如何調用。

第一:字符串指針作爲輸入參數,可以使用byte[] 和MarshalAs來解決。(注意四個P-INVOKE,兩個ANSI版本,和兩個UNICODE版本),推薦使用MarshalAs方法簡單明瞭。

1 [DllImport("TestDll", EntryPoint = "PrintString")]
2 public static extern void PrintStringByBytes(byte[] hello);
3
4 [DllImport("TestDll", EntryPoint = "PrintString")]
5 public static extern void PrintStringByMarshal([MarshalAs(UnmanagedType.LPStr)]string hello);
6
7 [DllImport("TestDll", EntryPoint = "PrintStringW")]
8 public static extern void PrintStringByBytesW(byte[] hello);
9
10 [DllImport("TestDll", EntryPoint = "PrintStringW")]
11 public static extern void PrintStringByMarshalW([MarshalAs(UnmanagedType.LPWStr)]string hello);
12
13
14 public void Run()
15 {
16 PrintStringByBytes(Encoding.ASCII.GetBytes("use byte[]"));
17 PrintStringByMarshal("use MarshalAs");
18 PrintStringByBytesW(Encoding.Unicode.GetBytes("use byte[]"));
19 PrintStringByMarshalW("use MarshalAs");
20 }

複製代碼

第二:字符串指針作爲返回值,和上面一樣也有兩種聲明方法,同樣也包含兩個版本。注意:Marshal.PtrToStringAnsi()函數的使用,把字符串指針轉變爲C#的string.推薦使用MarshalAs方法簡單明瞭。

1 [DllImport("TestDll", EntryPoint = "GetStringReturn")]
2 public static extern IntPtr GetStringReturnByBytes();
3
4 [DllImport("TestDll", EntryPoint = "GetStringReturn")]
5 [return:MarshalAs(UnmanagedType.LPStr)]
6 public static extern string GetStringReturnByMarshal();
7
8 [DllImport("TestDll", EntryPoint = "GetStringReturnW")]
9 public static extern IntPtr GetStringReturnByBytesW();
10
11 [DllImport("TestDll", EntryPoint = "GetStringReturnW")]
12 [return: MarshalAs(UnmanagedType.LPWStr)]
13 public static extern string GetStringReturnByMarshalW();
14
15
16 public void Run()
17 { //Marshal.PtrToStringAuto(GetStringReturnByBytes()); 自動判斷類型不錯。
18 Console.WriteLine(Marshal.PtrToStringAnsi(GetStringReturnByBytes()));
19 Console.WriteLine(GetStringReturnByMarshal());
20 Console.WriteLine(Marshal.PtrToStringUni(GetStringReturnByBytesW()));
21 Console.WriteLine(GetStringReturnByMarshalW());
22 }

複製代碼

第三:字符串指針作爲輸入輸出參數時,因爲要求有固定的容量,所以這裏使用的是StringBuilder,大家仔細看了,當然也有byte[]版本。這個看大家喜歡那個版本就是用那個.

1 [DllImport("TestDll", EntryPoint = "GetStringParam")]
2 public static extern void GetStringParamByBytes(byte[] outHello, int len);
3
4 [DllImport("TestDll", EntryPoint = "GetStringParam")]
5 public static extern void GetStringParamByMarshal([Out, MarshalAs(UnmanagedType.LPStr)]StringBuilder outHello, int len);
6
7 [DllImport("TestDll", EntryPoint = "GetStringParamW")]
8 public static extern void GetStringParamByBytesW(byte[] outHello, int len);
9
10 [DllImport("TestDll", EntryPoint = "GetStringParamW")]
11 public static extern void GetStringParamByMarshalW([Out, MarshalAs(UnmanagedType.LPWStr)]StringBuilder outHello, int len);
12
13
14 public byte[] _outHello = new byte[10];
15 public byte[] _outHelloW = new byte[20];
16 public StringBuilder _builder = new StringBuilder(10); //很重要設定string的容量。
17
18 public void Run()
19 {
20 //
21 GetStringParamByBytes(_outHello, _outHello.Length);
22 GetStringParamByMarshal(_builder, _builder.Capacity);
23 GetStringParamByBytesW(_outHelloW, _outHelloW.Length / 2);
24 GetStringParamByMarshalW(_builder, _builder.Capacity);
25
26 //
27 Console.WriteLine(Encoding.ASCII.GetString(_outHello));
28 Console.WriteLine(_builder.ToString());
29 Console.WriteLine(Encoding.Unicode.GetString(_outHelloW));
30 Console.WriteLine(_builder.ToString());
31 }
32

複製代碼

下載:代碼

c#編程指南(十一) 平臺調用P-INVOKE完全掌握, 指針大全

這篇是講述P-INVOKE中,應對各種指針的方法。包括普通指針,字符串指針,二級指針,指針數組,函數指針,結構體指針。篇幅估計有點長,大家耐心點看。嘿嘿~~

第一:普通指針,包括char *,short *,int *,__int64 *,這些指針進行平臺調用是都對應C#的IntPtr類型,然後使用Marshal.ReadXXX()系列函數讀取就可,寫內存時使用Marshal.WriteXXX()系列函數就行。

c++:

28 static int test5 = 100;
29 int * __stdcall ReadInt()
30 {
31 return &test5;
32 }

複製代碼

c#:注意Marshal.ReadXXX()系列函數的使用

15 [DllImport("TestDll")]
16 public static extern IntPtr ReadInt();
29 //##############################
42 IntPtr p5 = ReadInt();
43 Console.WriteLine(Marshal.ReadInt32(p5));
44 IntPtr p6 = ReadUint();
45 Console.WriteLine(Convert.ToUInt32(Marshal.ReadInt32(p6)));

複製代碼

第二:字符串指針上一篇已經有過討論,可以使用marshalAs返回值,當然也可以用Marshal.PtrToStringAuto()來讀取字符串。個人推薦第一個。

c++:

1 static char * test9 = "you are very very bad bad girl, gaga!";
2 char * __stdcall ReadString()
3 {
4 return test9;
5 }
6
7
8 static wchar_t * test10 = TEXT("you are very very bad bad girl, gaga!");
9 wchar_t * __stdcall ReadStringW()
10 {
11 return test10;
12 }

複製代碼

c#:注意Marshal.PtrToStringAuto()函數的使用。

1 [DllImport("TestDll")]
2 [return: MarshalAs(UnmanagedType.LPStr)]
3 public static extern string ReadString();
4
5 [DllImport("TestDll")]
6 public static extern IntPtr ReadStringW();
7 //#########################
8 Console.WriteLine(ReadString());
9
10 IntPtr p9 = ReadStringW();
11 Console.WriteLine(Marshal.PtrToStringAuto(p9));

複製代碼

第三:函數指針,C#的委託就對應C++的__stdcall調用協議的函數指針。前面也有討論。

c++:

1 typedef void (__stdcall * FunctionPoint)(void);
2 void __stdcall TestFunction(){printf("you are very very bad bad girl, gaga!\n");}
3 FunctionPoint __stdcall GetFunctionPointer()
4 {
5 return TestFunction;
6 }

複製代碼

c#:

1 public delegate void FunctionPointer();
2 [DllImport("TestDll")]
3 public static extern FunctionPointer GetFunctionPointer();
4 //#########################
5 FunctionPointer pointer = GetFunctionPointer();
6 pointer();

複製代碼

第四:指針的指針,也就是二級指針,要使用Marshal.ReadIntPtr()來讀取。

c++:

1 static int test11 = 555;
2 static int * test12 = &test11;
3 int ** __stdcall ReadPoint2()
4 {
5 return &test12;
6 }

複製代碼

c#:

1 [DllImport("TestDll")]
2 public static extern IntPtr ReadPoint2();
3 //#########################
4 IntPtr p10 = ReadPoint2();
5 IntPtr p11 = Marshal.ReadIntPtr(p10);
6 int test10 = Marshal.ReadInt32(p11);
7 Console.WriteLine(test10);

複製代碼

第五:指針數組沒有太直接的方法,因爲C++所有指針都是4BYTE所以這裏我是這麼讀的。

C++:

1 static int test13 = 666;
2 static int test14 = 777;
3 static int * test15[3] = {0};
4 int ** __stdcall ReadPointArray()
5 {
6 test15[0] = &test11;
7 test15[1] = &test13;
8 test15[2] = &test14;
9 return test15;
10 }

複製代碼

C#:

1 [DllImport("TestDll")]
2 public static extern IntPtr ReadPointArray();
3 //#########################
4 IntPtr p12 = ReadPointArray();
5 int test11 = p12.ToInt32();
6 Console.WriteLine(Marshal.ReadInt32(Marshal.ReadIntPtr(p12)));
7 Console.WriteLine(Marshal.ReadInt32(Marshal.ReadIntPtr(new IntPtr(test11 + 0))));
8 Console.WriteLine(Marshal.ReadInt32(Marshal.ReadIntPtr(new IntPtr(test11 + 4))));
9 Console.WriteLine(Marshal.ReadInt32(Marshal.ReadIntPtr(new IntPtr(test11 + 8))));

複製代碼

最後: 結構體的指針,用C#建立對應C++的結構體,並使用 [StructLayout(LayoutKind.Sequential)]屬性,

使用Marshal.PtrToStructure()讀取。

c++:

1 struct Test
2 {
3 int test;
4 };
5
6 static Test test16;
7 Test * __stdcall ReadStruct()
8 {
9 test16.test = 1234;
10 return &test16;
11 }

複製代碼

c#:

1 [StructLayout(LayoutKind.Sequential)]
2 public struct Test
3 {
4 public int test;
5 }
6
7 [DllImport("TestDll")]
8 public static extern IntPtr ReadStruct();
9 //#########################
10
11 IntPtr p13 = ReadStruct();
12 Test test13 = (Test)Marshal.PtrToStructure(p13,typeof(Test));
13 Console.WriteLine(test13.test);

複製代碼

c#編程指南(十二) 平臺調用P-INVOKE完全掌握, 結構體邊界對齊和內存佈局

在使用結構體指針,進行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,很強大也很繁瑣。

下載:下載

c#編程指南(十三) 平臺調用P-INVOKE完全掌握, 結構體和結構體指針

這篇講關於結構體和結構體指針的P-INVOKE,關鍵有4個P-INVOKE類型,結構體作爲輸入輸出參數。結構體指針作爲輸入輸出參數。還有結構體內的成員類型分爲:數組,指針,指針數組,結構體,結構體指針,結構體數組,結構體指針數組。當然還有類繼承(這裏只介紹了單繼承)。
其中有一個比較費解的是結構體作爲返回值的P-INVOKE的奇怪現象,下一篇結合反彙編講解。

第一:C++結構體和C#結構體對應關係,看下面。這裏提到一點C# 聲明結構體中的成員是數組的必須像下面那樣聲明:使用[MarshalAs(UnmanagedType.ByValArray, SizeConst = N)]

C++代碼不多,全部貼到這裏:

1 struct Base
2 {
3 int BaseInt;
4 };
5
6 struct Test : Base
7 {
8 int TestIntArray[2];
9 //
10 int * TestIntPointer;
11 int * TestIntPointerArray[2];
12 //
13 Base TestBase;
14 Base * TestBasePoint;
15 Base TestBaseArray[2];
16 Base * TestBasePointerArray[2];
17 };

複製代碼

再來看C#的結構體聲明:

1 [StructLayout(LayoutKind.Sequential)]
2 public struct Base
3 {
4 public int BaseInt;
5 }
6
7 [StructLayout(LayoutKind.Sequential)]
8 public struct Test
9 {
10 public Base _base;//把繼承的基類放在第一個元素的位置。
11 //
12 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
13 public int[] TestIntArray;
14 //
15 public IntPtr TestIntPointer;
16 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
17 public IntPtr[] TestIntPointerArray;
18 //
19 public Base TestBase;
20 public IntPtr TestBasePoint;
21 //
22 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
23 public Base[] TestBaseArray;
24 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
25 public IntPtr[] TestBasePointerArray;
26 }
27

複製代碼

第二。C++導出函數和C# P-INVOKE函數的對應。
C++:

1 static Test _test;
2 void SetTest(Test test)
3 {
4 _test = test;
5 PrintTest();
6 }
7
8 void SetTestPointer(Test * lptest)
9 {
10 _test = * lptest;
11 PrintTest();
12 }
13
14 Test GetTest()
15 {
16 return _test;
17 }
18
19 Test * GetTestPointer()
20 {
21 return &_test;
22 }

複製代碼

C#: 注意結構體作爲返回值的P-INVOKE聲明是不是很奇怪。不過運行很正常。下一篇結合反彙編說。

1 [DllImport("TestDll")]
2 public static extern void SetTest(Test test);
3
4 [DllImport("TestDll")]
5 public static extern void SetTestPointer(IntPtr lptest);
6
7 [DllImport("TestDll")]
8 public static extern void GetTest(IntPtr lptest); //注意聲明。
9
10 [DllImport("TestDll")]
11 public static extern IntPtr GetTestPointer();

複製代碼

第三:看下C#如何調用,這裏用到了Marshal.AllocHGlobal 方法,和Alloc功能基本一樣,會造成內存泄露,使用完了記住使用Marshal.FreeHGlobal函數釋放申請的內存。

1 private Test _test = new Test();
2
3 public void Run()
4 {
5 InitTest();
6 //#########################
7 SetTest(_test);
8 Console.WriteLine("-------------------------------------------------------------\n");
9 //#########################
10 _test._base.BaseInt = 9999;
11 //Marshal.AllocHGlobal 和WIN32 API, Alloc功能基本一樣,
12 //這個方法不要多用,可能造成內存泄露。
13 //記住使用Marshal.FreeHGlobal函數釋放申請的內存
14 IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Test)));
15 Marshal.StructureToPtr(_test,p,false);
16 SetTestPointer(p);
17 Console.WriteLine("-------------------------------------------------------------\n");
18 //#########################
19 IntPtr pp = GetTestPointer();
20 Test temp = (Test)Marshal.PtrToStructure(pp, typeof(Test));
21 PrintTest(temp);
22 Console.WriteLine("-------------------------------------------------------------\n");
23
24 //#########################
25 IntPtr pp2 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Test)));
26 GetTest(pp2);
27 Test temp2 = (Test)Marshal.PtrToStructure(pp2, typeof(Test));
28 PrintTest(temp2);
29
30 }

複製代碼

總結一下:Marshal.StructureToPtr從託管類複製數據到未託管的內存中,Marshal.PtrToStructure恰好相反。Marshal.AllocHGlobal申請非託管內存,Marshal.FreeHGlobal函數釋放非託管內存。使用Marshal.Read系列讀寫指針,使用Marshal.ReadIntPtr來讀寫二級指針。

c#編程指南(十四) 平臺調用P-INVOKE完全掌握, 反彙編細解結構體作爲返回值

這篇解決上篇那個結構體作爲返回值的問題。我們結合反彙編來探索這裏面的祕密。如何反彙編?

方法如下:在C++函數內下斷點,調試到斷點斷下,右鍵菜單,選擇"反彙編",反彙編是VS自帶功能。

下面是幾個簡單的類:

1 struct Test1
2 {
3 int Count1;
4 };
5
6 struct Test2
7 {
8 int Count1;
9 int Count2;
10 };
11
12 struct Test3
13 {
14 int Count1;
15 int Count2;
16 int Count3;
17 };

複製代碼

分別用上面的方法來逐個分析彙編:彙編裏有我詳細的註釋:

1
2 當返回Test1的時候:彙編如下:
3 函數內:
4 ;----------------------------GetTest函數內-------------------------------------------------
5 _test.Count = 3333;
6 00411B0E mov dword ptr [_test (4174C4h)],0D05h
7 return _test;
8 00411B18 mov eax,dword ptr [_test (4174C4h)] ; //把返回值放到EAX寄存器
9 ;---------------------------主函數調用GetTest----------------------------------------------
10 Test test = GetTest();
11 004135FE call GetTest (4110E1h)
12 00413603 mov dword ptr [ebp-0D4h],eax ;//將返回值存儲在棧中分配的變量中。
13 ;---------------------------------------------------------------------------------------------------
14
15 看來雖然Test1是結構體,但是由於就佔4個字節,所以通過EAX正常返回。
16
17
18 看下Test2的反彙編:
19 ;----------------------------GetTest函數內-------------------------------------------------
20 _test.Count = 3333;
21 00411B0E mov dword ptr [_test (4174C4h)],0D05h
22 _test.Count2 = 4444;
23 00411B18 mov dword ptr [_test+4 (4174C8h)],115Ch
24 return _test;
25 00411B22 mov eax,dword ptr [_test (4174C4h)] ;返回值通過EAX,EDX返回
26 00411B27 mov edx,dword ptr [_test+4 (4174C8h)]
27 ;---------------------------主函數調用GetTest----------------------------------------------
28 Test test = GetTest();
29 004135FE call GetTest (4110E1h)
30 00413603 mov dword ptr [ebp-0DCh],eax ;返回值存入棧中變量
31 00413609 mov dword ptr [ebp-0D8h],edx
32 ;---------------------------------------------------------------------------------------------------
33
34 雖然Test2佔8個字節,但是編譯器通過組合EAX,EDX可以正確返回。
35
36
37 看下Test3的反彙編:
38 ;----------------------------GetTest函數內-------------------------------------------------
39 00411470 push ebp
40 00411471 mov ebp,esp ;構建棧幀
41
42 00411473 sub esp,0C0h ;分配局部變量
43
44 00411479 push ebx
45 0041147A push esi
46 0041147B push edi ;保存寄存器環境
47
48 0041147C lea edi,[ebp-0C0h]
49 00411482 mov ecx,30h
50 00411487 mov eax,0CCCCCCCCh
51 0041148C rep stos dword ptr es:[edi] ;設置trap area.
52
53 _test.Count1 = 3333;
54 0041148E mov dword ptr [_test (417140h)],0D05h
55 _test.Count2 = 4444;
56 00411498 mov dword ptr [_test+4 (417144h)],115Ch
57 _test.Count3 = 5555;
58 004114A2 mov dword ptr [_test+8 (417148h)],15B3h
59
60
61 return _test;
62 004114AC mov eax,dword ptr [ebp+8] ;得到第一個參數,注意__stdcall從右向左壓棧.
63
64 004114AF mov ecx,dword ptr [_test (417140h)]
65 004114B5 mov dword ptr [eax],ecx ;參數是一個指針,寫入第一個成員;
66 004114B7 mov edx,dword ptr [_test+4 (417144h)]
67 004114BD mov dword ptr [eax+4],edx ;參數是一個指針,寫入第二個成員;
68 004114C0 mov ecx,dword ptr [_test+8 (417148h)]
69 004114C6 mov dword ptr [eax+8],ecx ;參數是一個指針,寫入第三個成員;
70 004114C9 mov eax,dword ptr [ebp+8]
71 ;---------------------------主函數調用GetTest----------------------------------------------
72 Test test = GetTest();
73 004113BE lea eax,[ebp-0E4h]
74 004113C4 push eax ;壓棧參數
75 004113C5 call GetTest (4110E1h)
76 004113CA add esp,4 ;回收棧幀
77
78 004113CD mov ecx,dword ptr [eax] ;返回參數。
79
80 004113CF mov dword ptr [ebp-0F8h],ecx ; 寫入局部變量test
81 004113D5 mov edx,dword ptr [eax+4]
82 004113D8 mov dword ptr [ebp-0F4h],edx
83 004113DE mov eax,dword ptr [eax+8]
84 004113E1 mov dword ptr [ebp-0F0h],eax
85 004113E7 mov ecx,dword ptr [ebp-0F8h]
86 004113ED mov dword ptr [test],ecx
87 004113F0 mov edx,dword ptr [ebp-0F4h]
88 004113F6 mov dword ptr [ebp-0Ch],edx
89 004113F9 mov eax,dword ptr [ebp-0F0h]
90 004113FF mov dword ptr [ebp-8],eax
91 return 0;
92 00411402 xor eax,eax
93 ;---------------------------------------------------------------------------------------------------
94
95 Test3佔12字節,無法正常通EAX,EDX返回,所以編譯器把函數編譯成帶輸入參數的函數,
96 就好像Test test = GetTest(&test);一樣。

複製代碼

總結一下,這個結構體作爲返回值,主要依賴於編譯器對於超過8字節的返回值的編譯處理。
第一:不建議使用結構體作爲返回值,因爲這太依賴編譯器了。

第二:如果沒法改變,就用如下的方法判斷:

//if (Marshal.SizeOf(typeof(X)) > 8)

//{

// 返回值作爲輸出參數。

//}

//else

//{

// 返回值正常返回。

//}

第三:<=8字節,正常的P-INVOKE,不用修改。

第四:>8字節,把返回值作爲從左向右的第一個輸入輸出參數:下面是簡單的示例

Test GetTest(); ====> IntPtr GetTest(IntPtr lptest);

Test GetTest(int i) ======> IntPtr GetTest(IntPtr lptest, int i);

最後:C#調用Test3 _stdcall GetTest3()爲什麼:

[DllImport("TestDll")]

public static extern IntPtr GetTest3(IntPtr lptest);

[DllImport("TestDll",EntryPoint="GetTest3")]

public static extern void Test3Extra(IntPtr p);

返回值不同卻都可以調用?嘿嘿!!

c#編程指南(十五) 平臺調用P-INVOKE完全掌握(完結篇),自定義Mashaler

這是P-INVOKE系列的最後一篇,也是萬劍歸宗的一篇,基本上只要函數簽名對了,用他可以傳遞任何參數,函數的輸入輸出參數和返回值你也可以隨心所欲地修改。

把這個放在最後也是最完美的結局吧!!

C++:測試代碼如下:

1 struct Test
2 {
3 int test;
4 };
5
6 //static Test _test;
7
8 Test GetTest(Test * lpTest)
9 {
10 lpTest->test = 200;
11 return * lpTest;
12 }

複製代碼

C#:在P-INVOKE中,我把C++指針參數Marshal成C#類的輸入參數,把C++返回值爲結構體的Marshal成字符串。注意下面的P-INVOKE聲明。

UnmanagedType.CustomMarshaler表明使用自定義的marshaler.

1 [StructLayout(LayoutKind.Sequential)]
2 public class Test
3 {
4 public int test;
5 }
6
7
8 public class PInvokeTest
9 {
10 [DllImport("TestDll")]
11 [return:MarshalAs(UnmanagedType.CustomMarshaler,MarshalTypeRef=typeof(TestMashaler),MarshalCookie="output")]
12 public static extern string GetTest([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TestMashaler), MarshalCookie = "input")] ref Test test);
13 //把返回值Marshal成字符串,自定義Marshal 參數。
14
15 public void Run()
16 {
17 Test test = new Test();
18 test.test = 100;
19
20 string temp = GetTest(ref test);
21 Console.WriteLine(test.test + "," + temp);
22 }
23
24 }

複製代碼

自定義marshaler要實現ICustomMarshaler接口,並且提供一個類函數public static ICustomMarshaler GetInstance(string cookie),

微軟方法的名字很好懂,我也不再一一解釋了。關鍵地方代碼中也有註釋。

1 public class TestMashaler : ICustomMarshaler
2 {
3 private static object _refObject = null;
4 private string _cookie = string.Empty;
5
6 public TestMashaler(string cookie)
7 {
8 _cookie = cookie; //保存是輸入參數使用的Mashaler,還是輸出參數使用的Mashaler.
9 }
10
11 public void CleanUpManagedData(object ManagedObj)
12 {
13 }
14
15 public void CleanUpNativeData(IntPtr pNativeData)
16 { //清理非託管內存,防止內存泄露
17 Marshal.FreeHGlobal(pNativeData);
18 }
19
20 public int GetNativeDataSize()
21 {
22 return Marshal.SizeOf(typeof(Test));
23 }
24
25 public IntPtr MarshalManagedToNative(object ManagedObj)
26 {
27 if (ManagedObj is Test)
28 { //保存ref引用。
29 if (_cookie == "input") _refObject = ManagedObj;
30 IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Test)));
31 Marshal.StructureToPtr(ManagedObj, p, false);
32 return p; //Marshal成指針。
33 }
34 else throw new NullReferenceException();
35 }
36
37 public object MarshalNativeToManaged(IntPtr pNativeData)
38 {
39 if (pNativeData != IntPtr.Zero)
40 {
41 int temp = pNativeData.ToInt32();
42 if (_cookie == "input")
43 { //輸出參數返回
44 (_refObject as Test).test = temp;
45 return _refObject;
46 }
47 else if (_cookie == "output")
48 { //返回值返回。
49 return pNativeData.ToString();
50 }
51 return null;
52 }
53 else throw new NullReferenceException();
54 }
55
56 public static ICustomMarshaler GetInstance(string cookie)
57 {
58 return new TestMashaler(cookie);
59 }
60 }

複製代碼

最後還是建議大家使用微軟提供的,自己實現的雖然很靈活很BT很邪門,但是還是很容易出錯。上面的代碼也僅供娛樂消遣了!!

c#編程指南(十七) 字符串和字節流相互轉化

字符串和字節流的轉化,主要用的是System.Text.Encoding類,下面的代碼示例,實現了UTF8,UTF32,GB2312常用編碼的文本轉化,代碼很簡單,

也不用解釋,放在這裏供大家參考,方便之餘,還是讚歎一個.net的給力吧!!

1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace TextEncoder
6 {
7 class Program
8 {
9 private static string _test = "Abc";
10 //
11 static void Main(string[] args)
12 {
13 //ASCII,UTF7,UTF8,UTF32,UNICODE
14 byte[] bytes1 = Encoding.ASCII.GetBytes(_test);
15 byte[] bytes2 = Encoding.UTF8.GetBytes(_test);
16 byte[] bytes3 = Encoding.Unicode.GetBytes(_test);
17 //
18 string s1 = Encoding.ASCII.GetString(bytes1);
19 string s2 = Encoding.UTF8.GetString(bytes2);
20 string s3 = Encoding.Unicode.GetString(bytes3);
21 //
22 //GB2312,BIG5
23 byte[] bytes4 = Encoding.GetEncoding("GB2312").GetBytes(_test);
24 byte[] bytes5 = Encoding.GetEncoding("BIG5").GetBytes(_test);
25 //
26 string s4 = Encoding.GetEncoding("GB2312").GetString(bytes4);
27 string s5 = Encoding.GetEncoding("BIG5").GetString(bytes5);
28 //
29 Console.WriteLine(s1 + ";" + s2 + ";" + s3 + ";" + s4 + ";" + s5);
30 }
31 }
32 }

複製代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章