看了P/Invoke技术的介绍,于是想写下点东西,东西包含两个部分:知识的纪录和我的理解及疑问。
r托管代码中调用非托管API函数的过程
1、定位包含API的DLL;
2、载入DLL
3、找到DLL中想要的那个API,然后把参数压入栈中、排列数据(排列数据是什么意思?数据封送)
4、把执行权限从托管代码中转移到非托管代码中()
对Dll中的函数进行一些说明,以能调用
DllImport特性来说明函数,有一些特殊的作用,比如换掉API的原来名字,见DLLImport特性。
非托管函数和托管方法中数据类型对应:数据封送
把C#中数据转换成API中数据类型,是数据封送。对于每个 .NET Framework 类型均有一个默认非托管类型,公共语言运行库将使用此非托管类型在托管到非托管的函数调用中封送数据。string 类型默认非托管类型是LPTSTR,可以在非托管函数的 C# 声明中使用 MarshalAs 属性重写默认封送处理。例如:
[DllImport("msvcrt.dll")]
public static extern int puts(
[MarshalAs(UnmanagedType.LPStr)]
string m);puts
函数的参数的默认封送处理已从默认值 LPTSTR 重写为 LPSTR
修改默认封送有什么用? 默认情况下,本机结构和托管结构在内存中的布局有所不同,因此,若要跨托管/非托管边界成功传递结构,需要执行一些额外步骤来保持数据的完整性。,
如果某个API中使用一个结构,那么C#中没有一个对应的托管类型与之对应的话,怎么办?需要为用户定义的结构指定自定义封送处理。
可以为传递到非托管函数或从非托管函数返回的结构和类的字段指定自定义封送处理属性。通过向结构或类的字段中添加 MarshalAs 属性可以做到这一点。还必须使用 StructLayout 属性设置结构的布局,还可以控制字符串成员的默认封送处理,并设置默认封装大小。
C++中:
1 struct SS 2 { 3 4 int a; 5 6 byte b; 7 8 }
对应C#中:
1 class SS 2 { 3 public int a; 4 public byte b; 5 }
封送,在此看来算是传数据,但是为什么要封送呢?封送是为了在托管内存和非托管内存中正常传递数据。?有时,出于对性能的考虑,会对托管结构的成员进行重新排列,因此有必要使用 StructLayoutAttribute 特性指示该结构为顺序布局。 将结构封装设置显式设置为与本机结构所使用的设置相同的设置也是一个好办法。
数据封送--基本数据类型对应关系:
数据封送中指针处理的两种情况 :
一、普通指针
在C#中使用ref out来实行封送。
二、Handle类型
C#中使用IntPtr来进行封送
封送,我的理解是两个方面,封装和传送。传送就像是方法中参数的传递那样;而封装就有些意思了,在封装前有时候需要做一些处理。就像是上面的 class SS和C++中的struct ss ,其实两者中的数据a、b在内存中是不一样的。详细如下:
1 1 class SS 2 2 { 3 3 public int a; 4 4 public byte b; 5 5 }
看起来a是在b的前面,其实在内存中可不是这样的,C#中类的字段成员的顺序在内存中是自动安排的,也就是说不像是源代码中那样,至于是为什么是自动(auto)安排顺序,还不知道,希望有人知道的话说一下,而在C++中类成员的顺序却是和看到的一样的,这就是为什么在封装前要做一些处理了,因为成员在内存中的顺序不一样!
C#中有一个特性可以调整这个顺序,也就是[StructLayout(LayoutKind.Sequential)],在没有显式进行修饰一个类时,默认情况下是[StructLayout(LayoutKind.Auto)]的。处理如下:
[StructLayout(LayoutKind.Sequential)] class SS { public int a; public byte b; }
注册回调方法
遇到回调函数的处理。
.NET学习之路----我对P/Invoke技术的理解(一)