.NET Compact Framework使用P/Invoke服务

一、 P/Invoke
.NET Compact Framework 的支持下,可以方便高效地开发出适合于移动设备的应用程序,而不需要去考虑特定的硬件环境。 .NET Compact Framework 向开发者屏蔽了硬件底层的细节,使开发者可以集中精力于业务逻辑的解决方案。
 
  作为 .NET Framework 的一个子集, .NET Compact Framework 只提供了 .NET Framework 的一部分功能,因此有时在实现一些功能时不得不借助于 Windows CE API 。另外还存在一些第三方的组件 / 资源,或以动态链接库形式提供,或者已经是 COM 组件。相对于 .NET Compact Framework ,它们都属于非托管资源。我们需要一种功能,实现由托管环境访问这些非托管资源。和 .NET Framework 一样,平台调用 P/Invoke(Platform Invocation Services) 提供托管代码调用驻留于 DLL 中的非托管函数的功能。下面是一张P/Invoke原理图,来自 MSDN


 
归纳起来,使用 P/Invoke 的场合包括:
1 .NET Compact Framework 没有实现某功能,需要借助 Windows CE API
2 、已有 DLL COM 组件等资源,希望能充分利用,减少开发成本和风险;
3 、鉴于 DLL 的执行性能和反编译能力都可能高于 .NET Compact Framework ,借助 DLL 提高程序性能和安全性。当然关于 DLL 的执行性能是否高于托管代码,不能一概而论。
 
二、 .NET Compact Framework 下的 P/Invoke
先看一个 P/Invoke 的例子。下面使用 DllImport 特征导入 Windows CE API 函数 MessageBoxW 的定义。
 
public class APIHelper
{
[DllImport("coredll.dll", SetLastError = true)]
public static extern int MessageBoxW(IntPtr hWnd, String text, String caption, uint type);
}

然后可以对它进行调用。
private void button1_Click(object sender, EventArgs e)
{
APIHelper .MessageBoxW(IntPtr.Zero, " 测试 MessageBoxW 函数 " ,
"api 调用 " , 0);
}

  可以看到,使用
P/Invoke 包括声明调用两个过程,另外还有一个错误处理的过程。通过声明来指定要调用的非托管函数, .NET Compact Framework 也是使用 DllImport 特性来进行声明,包括模块名、函数名及调用约定。与 .NET Framework 完整版的 DllImport 特性不同, .NET Compact Framework 的一共包括五个公共字段: CallingConvention CharSet EntryPoint PreserveSig SetLastError 。具体各字段的说明可以参考 MSDN
 
EntryPoint 可以指定为函数名或函数的序号值,如 EntryPoint = "MessageBoxW" EntryPoint = "#858" 。值得注意的是 .NET Compact Framework CallingConvention 只支持 CallingConvention.Winapi ,即默认的平台调用;编码方式只支持 Unicode ,因此 CharSet 实际只有 CharSet.Unicode 一个取值。因此在导入定义时省略 CallingConvention CharSet 字段的效果没有分别。
 
另外, DllImport 修饰的方法必须用 static extern 关键字来指明方法是在外部实现的,对其可见性修饰符则没有限制。
 
调用 DllImport 导入的非托管函数时, CLR P/Invoke 服务从声明中提取出元数据,定位要调用的模块( coredll.dll ),将其加载到内存,然后根据入口点信息检索非托管函数地址。如果不出现错误,则 P/Invoke 完成参数的封送并调用该函数,并把返回函数的返回值。
 
P/Invoke 会产生两种错误,一种是上面说到的 P/Invoke 在定位调用模块,检索函数地址时出错。如 P/Invoke 找不到入口点时会出错,并抛出 MissingMethodException 异常;函数的调用约定声明有误时会抛出 NotSupportedException 异常,这时应检查函数的参数及返回值定义是否与模块中函数吻合。 P/Invoke 的另一种错误是执行非托管函数过程中发生的错误。
 
另一个需要特别注意的是, .NET Compact Framework P/Invoke 不支持回调,即无法向非托管函数传递一个委托并在非托管函数中被调用。使用需要回调的非托管函数时会引发异常。
 
三、 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_t TCHAR(16 UNICODE 字符 )
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 CE API
 
coredll.dll Windows CE 的核心模块,大致相当于 Windows 2000/XP kernel32.dll coredll.dll Windows 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 模拟器下运行通过。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值