.NET Compact Framework使用P/Invoke服务

2024-04-14 13:58

本文主要是介绍.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、已有DLLCOM组件等资源,希望能充分利用,减少开发成本和风险;

3、鉴于DLL的执行性能和反编译能力都可能高于.NET Compact Framework,借助DLL提高程序性能和安全性。当然关于DLL的执行性能是否高于托管代码,不能一概而论。

 

二、.NET Compact Framework下的P/Invoke

先看一个P/Invoke的例子。下面使用DllImport特征导入Windows CEAPI函数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的一共包括五个公共字段:CallingConventionCharSetEntryPointPreserveSigSetLastError。具体各字段的说明可以参考MSDN

 

EntryPoint可以指定为函数名或函数的序号值,如EntryPoint = "MessageBoxW"EntryPoint = "#858"。值得注意的是.NET Compact Framework CallingConvention只支持CallingConvention.Winapi,即默认的平台调用;编码方式只支持Unicode,因此CharSet实际只有CharSet.Unicode一个取值。因此在导入定义时省略CallingConventionCharSet字段的效果没有分别。

 

另外,DllImport修饰的方法必须用 static extern 关键字来指明方法是在外部实现的,对其可见性修饰符则没有限制。

 

调用DllImport导入的非托管函数时,CLRP/Invoke服务从声明中提取出元数据,定位要调用的模块(coredll.dll),将其加载到内存,然后根据入口点信息检索非托管函数地址。如果不出现错误,则P/Invoke完成参数的封送并调用该函数,并把返回函数的返回值。

 

P/Invoke会产生两种错误,一种是上面说到的P/Invoke在定位调用模块,检索函数地址时出错。如P/Invoke找不到入口点时会出错,并抛出MissingMethodException异常;函数的调用约定声明有误时会抛出NotSupportedException异常,这时应检查函数的参数及返回值定义是否与模块中函数吻合。P/Invoke的另一种错误是执行非托管函数过程中发生的错误。

 

另一个需要特别注意的是,.NET Compact FrameworkP/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_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模拟器下运行通过。

这篇关于.NET Compact Framework使用P/Invoke服务的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/903167

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

使用C#代码在PDF文档中添加、删除和替换图片

《使用C#代码在PDF文档中添加、删除和替换图片》在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文将介绍如何在.NET平台使用C#代码在PDF文档中添加、... 目录引言用C#添加图片到PDF文档用C#删除PDF文档中的图片用C#替换PDF文档中的图片引言在当

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定