.NET Compact Framework使用P/Invoke服务 (二)

来源:互联网 发布:python 截断字符串 编辑:程序博客网 时间:2024/04/30 20:19
三、P/Invoke的参数封送
我们知道,托管代码与非托管代码存在很大的差异,P/Invoke在传递参数、返回值时需要先在托管类型和非托管类型之间进行转换,这整个过程有个专门术语,就叫做封送(Marshal)
P/Invoke可以为常规的数据类型进行正确地封送处理,如整型int,字节型byte。这些常规的数据类型被称为blittable 类型,它们在托管代码和非托管代码中具有相同的表示,P/Invoke在对它们进行封送时不需要进行任何特殊处理。因此使用blittable 类型的非托管函数以用相同表示的托管类型导入其定义。假如有一个DLL中的函数的C++签名为:
int DllFunction_1(int index);
按如下方式导入定义:
[DllImport("dllfile.dll")]
public static extern int DllFunction_1(int i);
在这种情况下,P/Invoke不需要进行任何特殊处理,可以正确地进行参数和返回值的封送。
具体blittable 类型包括:

托管代码类型
C++代码类型
System.Byte
unsigned char
System.SByte
byte
System.Int16
short
System.UInt16
unsigned short
System.Int32
int
System.Int64
long
System.UInt64
unsigned long
System.IntPtr
指针、句柄
System.Boolean
bool
System.Char
wchar_tTCHAR(16UNICODE字符)
System.String*
TCHAR *LPWSTR(UNICODE字符数组)

不仅以blittable 类型本身作为参数或返回值的非托管函数可以用相同表示的托管类型导入其定义,只包含blittable 类型(除标记*System.String外)字段成员的构成的类或结构同样遵守这条规则。需要特别说明的是System.String是一个特例,它作为字段成员构成类或结构时不遵守这条规则。
然而,其它的类型无法不能直接按这种对应方式导入调用约定。假如有一个DLL中的函数的C++签名为:
double DllFunction_2(int index);
按如下方式导入定义:
[DllImport("dllfile.dll")]
public static extern double DllFunction_2(int i);
在这种情况下,会引发一个NotSupportedException异常。这是因为托管世界的double类型和非托管世界的double类型(这里是C++)有着显著的不同,P/Invoke无法正确地将返回值从非托管的double类型转换到托管double类型,因此调用失败。应该说,P/Invoke同样无法正确的完成参数从托管double类型转换到非托管的double类型,但测试发现,实际上是可以完成的。如下C++签名为:
int DllFunction_3(double index);
按如下方式导入定义:
[DllImport("dllfile.dll")]
public static extern int DllFunction_3(double d);
作为参数值的托管double类型实际上可以被正确转换为非托管的double类型,P/Invoke正确地完成了封送。但MSDN文档明确说明.NET Compact Framework P/Invoke是无法支持对浮点类型通过值进行封送处理的,因此我们不能依赖作为参数的double类型可以被封送这个特例。在需要在托管世界和非托管世界之间传递double类型时,应该借助引用来对其进行封送,该引用将由P/Invoke封送为非托管世界的指针。下面是一个处理的例子
//原有函数,由于返回值是double类型,无法被封送,需要改写
double DllFunction_2(int index);
//DllFunction_2函数加上一个包装,改为以指针作为参数值递返回值
void DllFunction_Sub(int index, double * result)
{
      *result = DllFunction_2(index);//这里调用原有函数
}
按如下方式导入定义,注意导入的函数是DllFunction_Sub
[DllImport("dllfile.dll")]
public static extern void DllFunction_Sub(int index, out double result);

  可以为托管代码的调用再增加一个包装,保持与实际调用函数具有相同的签名:
public static double DllFunction_2(int index)
{
double result;
DllFunction_Sub(index, out result);
return result;
}

通过增加一个包装这种间接的方式,我们利用P/Invoke可以对引用进行封送的特性,完成了对实际具有如下签名的非托管函数的调用:
double DllFunction_2(int index);

四、使用P/Invoke调用Windows CEAPI
coredll.dllWindows CE的核心模块,大致相当于Windows 2000/XPkernel32.dllcoredll.dllWindows CE系统最重要的文件,基本上每个CE系统都会在ROM中包括该文件。在Pocket PC 2003 Second Edition(Window CE 4.2,以下简称PPC)系统中,其全路径为/windows/coredll.dll。由于该文件在ROM中,因此使用PPC自带的资源管理器无法看到该文件。可以通过TotalCommander查看该文件,但仍然无法复制该文件。在安装了platform builder后可以在安装目录下找到coredll.dll文件。
绝大多数的Windows CE API都是通过coredll.dll向外暴露。因此在使用P/Invoke调用coredll.dll中的api时,值得关心的是该文件中所包含的api函数。可以通过dumpbin.exe来查看其导出符号。
在无法直接得到coredll.dll时,可以通过该文件对应的LIB导入文件进行分析而获知coredll.dll包含的api函数。安装vs2005后,可以在[安装目录]/SmartDevices/SDK/PocketPC2003/Lib/armv4下找到coredll.lib文件,它就是coredll.dll对应的LIB导入文件。运行vs2005命令行工具,执行dumpbin /exports [安装目录]/SmartDevices/SDK/PocketPC2003/Lib/armv4/coredll.lib即可得到coredll.dll包含函数的列表。如果进一步需要得到某个api函数的参数及返回值类型则需要进行深入逆向工程分析了,就比较复杂。好在这些可以从微软的MSDN文档中得到某api的具体说明。
当需要确定其它dll中有什么函数时,同样可以通过使用dumpbin导出查看其中包含的函数。
文中代码全部在vs2005(C#智能设备Pocket PC 2003应用程序和MFC智能设备DLL)PPC模拟器下运行通过。

 

原创粉丝点击