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 }