概要
P/Invoke的机制让我们能在托管环境下使用原先已实现的Native Code。本文主要讨论的是P/Invoke中的参数传递和.NET CF的一些不同于完整版本的 .NET Fx之处,最后介绍了如何提高P/invoke的效率
Keywords
.NET Compact Framework, Windows Mobile, P/Invoke ,data marshaling
正文
好吧,先看个例子。为了获得用户按键的状态,下面的代码段演示了将GetAsyncKeyState函数从Coredll.dll中导出,并在托管代码中重命名为 GetMyKeyState供调用:
1usingSystem.Runtime.InteropServices;
2
3[DLLImport("coredll.dll",EntryPoint="GetAsyncKeyState")]
4publicstaticexternshortGetMyKeyState(intnKey)
5
6
7//调用时候如下:
8inti=2;
9shortnkeyState=GetMyKeyState(i);
10
在写下上面这几行代码之前,应该确定的是
· 所调用的dll确实存在,且路径正确
· 该dll所依赖的其他dll存在
· 入口点的函数名正确
· 传递给函数的参数在类型上是正确的
上述任何一点出错,都会导致MissingMethodException异常。前三条都比较好理解,往往让人困惑的是在参数的传递上。本地(native) 的libraries只能访问本地数据, 而默认状态下托管代码也只能访问托管的data. 为了使人们在托管的代码环境中仍然能访问到本地的类库,通常需要进行两种环境下的数据类型映射,这就是所谓的marshaling.
Marshaling一个对象的过程就是一个序列化(deflating)的过程, 相应的unmarshaling 就可以看作是反序列化(inflating)的过程.
对于一些简单的数据类型和对象, marshaling通常已经由.NET Framework自动完成了. 对于一些复杂的数据类型,可以使用Marshal类将托管的数据复制到非托管的内存空间上或者将非托管的数据复制到托管的内存空间上。
需要注意的是: 在完整版的.NET Framework中,长整形(64-bit integer) 和浮点型 (float and double)可以直接进行值传递.而在 .NET Compact Framework中, 他们则需要通过引用传递的方式实现”值传递”。对于这种引用传递,CSharp提供了两种方式. 关键字 out 仅用于从函数中将值回传出来. 关键字 ref 则既可以用来传入参数,也可以用来从函数中获取值,但是在之前,你必须先给ref修饰的参数指定一个值,例如: mean只是作为函数的 一个操作产物获取出来
下面来看看在本地代码中应当如何操作以供调用
简单的int型值传递:在函数声明的前面,添加了一段extern "C"__declspec(dllexport),作用是以C风格的形式将函数暴露以供外部调用
对于constant的常量值类型,在C/C++中我们通常这样定义它们:而在托管代码中我们直接使用const关键字修饰这种常量在C#中也可以简单的定义在一个枚举类型里面:前面都是讲的值类型的传递的例子,下面来看看引用类型是如何Marshal的
1. 数组
方案:
C/C++参数:头指针,长度
C#参数:数组实例,长度
以下函数用来计算数组的平均值:C#中调用如下:2. 字符串
在Windows Mobile的系统下只支持 Unicode 的字符编码。
方案:
C/C++参数:WCHAR */CString
C#参数:如果在本地代码中可能要变化字符串则用StringBuilder,静止的(unmutable)字符串值就直接用String传。
可以使用 System.String 和System.Text.StringBuilder 类来传递 Unicode 字符序列给本地代码。.NET CompactFramework的运行时会自动附加上一个表示终结的字符’/0’到该字符序列的后面,这样就是我们熟知的C 风格的string了,值得一提的是在 .NET Compact Framework中, System.String类型的对象从设计上采取了不可变模式, 也就是说它们在运行时值不可被改变. 如果程序运行的时候它们的数据需要被改变, 比如附加一个串到它结尾或者要移除某些字符,那么 .NET CLR 会为这样的操作产生一个新的对象拷贝. 所以你不能直接传入一个string给可能改变它的非托管代码。
1doublea=4.0;
2doubleb=4.0;
3myRef.DoubleMean2(refa,refb,outmean);
4publicclassmyRef
5{
6[DllImport("nativetest1.dll")]
7publicstaticexternvoidDoubleMean2(refdoublea,refdoubleb,outdoublemean);
8}
9
这里的参数
1extern"C"__declspec(dllexport)intIntAdd(intx,inty)
2{
3return(x+y);
4}
5//注意double型的参数为指针形式:
6extern"C"__declspec(dllexport)
7voidDoubleMean2(double*x,double*y,double*mean)
8{
9*mean=(*x+*y)/2.0;
10}
11
1#defineSUNDAY0
2#defineMONDAY1
3#defineTUESDAY2
4#defineWEDNESDAY3
5#defineTHURSDAY4
6#defineFRIDAY5
7#defineSATURDAY6
8
1constintSUNDAY=0;
2constintMONDAY=1;
3constintTUESDAY=2;
4constintWEDNESDAY=3;
5constintTHURSDAY=4;
6constintFRIDAY=5;
7constintSATURDAY=6;
1enumDates:int
2{
3SUNDAY=0,
4MONDAY=1,
5TUESDAY=2,
6WEDNESDAY=3,
7THURSDAY=4,
8FRIDAY=5,
9SATURDAY=6,
10}
11
1extern"C"__declspec(dllexport)
2intMeanArray(int*pItem,intlen)
3{
4if(len<1)
5return-1;//Empty
6intSum=0;
7for(inti=0;i<len;i++)
8Sum+=pItem[i];
9returnSum/len;
10}
[DllImport("MarshalTypeDll.dll")]
externstaticintMeanArray(int[]pItem,intlen);
int[]stuScores=newint[]{78,85,51,92,81,96,65};
intmean=MeanArray(stuScores,stuScores.Length);
MessageBox.Show(String.Format("Theclassaverageoffinalexamis{0}",mean));
3. 传结构和类
方案:使用 MarshalAsAtrribute
对于那些只含有简单又通用(通用指的是有相同内存存储结构)的数据类型,直接写就是了。但是很多情况下,我们遇到的都是一些包含复杂元素的数据类型。这时就需要用到MarshalAsAttribute和 UnmanagedType 枚举了.下面一行C#代码的作用是传递一个两个字节空截止(Null-terminated)的Unicode 字符序列到非托管代码中:
void PInvokeAnCFunction([MarshalAs(UnmanagedType.LPWStr)] string s);
再看一个稍微复杂点的
在C++里面我们这样描述:看看我们在 C#中是如何定义这个结构的:这里要注意当使用 ByValArray 或者ByValTStr时, 你必须指定 sizeConst 因为它们都是定长的数据类型。
structComplexStruct
{
intintAry[10];
charcharAry[80];
WCHAR*pStr;
};
structComplexStruct
{
[MarshalAs(UnmanagedType.ByValArray,sizeConst=10)]int[]intAry;
[MarshalAs(UnmanagedType.ByValTStr,sizeConst=80)]stringstr1;
[marshalAs(UnmanagedType.LPWStr)]stringstr2;
};
4. 最后来一个特殊的,浮点数(float):
.NET CF不能直接Marshal为浮点数,但可以通过指针传递,用IntPtr承接指针,再从IntPtr去Marshal成String。
好了,似乎有关.NET Compact Framework的p/invoke的方法和数据混合(Marshal)已经介绍得差不多了。不过在实际的工程中还有一个重要的问题需要考虑,那就是P/invoke的效率。一般来说,追求便捷的高效开发总是伴随着程序效率的降低,MS的一些研究表明,从CLR调用和执行非托管的DLL比执行托管的的内容要慢5倍左右,虽然在PC上这根本不算什么,但是在移动设备上这种差别有时简直就是我们能用“肉眼”识别的。
究竟是什么造成了P/inveke中性能的损失呢?主要有两个方面的原因: 一方面,损耗来自于混合(marshaling) 托管数据成非托管的数据的时候。显然,参数越多,计算所用的时间和内存就会越耗,尤其是那些非通用数据类型的参数。另一方面, 由于托管的数据是由.NET CLR 控制的,像垃圾回收(GC)这样的操作一直在密切‘监视’着这些数据当我们在托管的代码中调用本地(Native)APIs的时候, .NET Compact Framework 首先会将调用的那些本地数据从GC中注销掉,也就是说调用的本地数据并不被托管的GC回收和释放,所以一些Free的工作需要在本地代码内部完成,当对本地数据的处理和解析(如marshal)结束之后, .NET runtime又会将这些数据重新声明为托管的数据类型,重新交给CLR处理,这一系列额外的操作代价是不小的。
为了将这种损失降到最小,应该怎么去做呢?显然,在数据混合(Marshal)的时候,我们应当尽量使用那些通用的数据类型,或者说是由CLR帮我们做了Marshal的数据类型。 其次,针对上述的第二个原因,少调用多封装也是优化的方案之一,因为每一次调用的厄外操作成本差不多。这就像来到大学的第一年把后面四年的注册手续都办好了,后面就不必那么麻烦了(事实上,我有同学真的是这么干的…Orz)。
最后再来看几条来自官方的建议,他们来自 Microsoft .NET Compact Framework team:
· 当参数是基本的通用类型,或者简单类型的时候,P/Invoke的调用会更快。这里所说的类型包括:
o 所有通用的64位值类型,除了long型 ,通过传引用(reference)的方式要比直接传值效率要高
o 简单数据类型,如 String 和Array, .NET CF 会很快的实现数据混合(Marshal)
o 仅包含上述两种类型的结构和类
· 给参数使用使用in 和 out 特性可以加速marshaling 的过程.
· Marshal.Prelink 和Marshal.PrelinkAll 在 .NET Compact Framework 2.0 中可以用来在程序启动的时候就做一些对本地函数的初始化的预链接,这样用户只需在程序一开始稍作等待,后面的调用是很迅速的。
总结
.NET Compact Framework 通过P/invoke的方式使得我们得以调用本地代码编写的类库,只需要导入à声明à调用的一个简单的过程即可实现。值得注意的是参数的传递,具有有相同内存存储结构的通用类型,可以直接传递,如int。 较复杂的数据类型如数组和字符串可以由引用(指针)的方式传递,对于结构和类,通过LayoutKind和 MarshalAs 两个特性标签可以让我们指定数据来内存中的映射关系。最后,P/invoke存在效率的问题,不要滥用,一般来说,用P/invoke去解决关键的一小部分托管代码不能完成的部分就可以了。
分享到:
相关推荐
P/Invoke Interop Assistant,它支持托管代码和非托管代码之间的方法签名的转换,而且直接生成相关的C#或者是VB的方法调用代码。这个签名的转换,不只是适用于Windows的方法签名,只要给定一个C头文件,就可以转换...
《精通.NET互操作P/Invoke,C++Interop和COM Interop》介绍Windows平台上的托管代码与非托管代码之间进行互操作的各种技术,包括由.NET提供的各种互操作方法、属性以及各种工具的用法及其工作原理。《精通.NET互操作...
托管代码与非托管代码互操作的辅助产生代码的工具
《精通.NET互操作:p/invoke, c++ interop和COM interop》一书的源代码
第5篇主要介绍了移动应用高级功能,如P/Invoke、POOM、资源与本地化、多线程编程、性能优化和移动安全策略等的开发。第6篇从实际... 目录 * 目录 序言 * 前言 第2章 .NET Compact Framework简介 * 2.1 概述 * 2.2...
P/Invoke Interop Assistant,用来调试C++写的动态库转换成C#或VB.NET
可以把C/C++中的数据类型、结构体数据格式转换为C#或者VB版本中的对应格式类型。很方便
《CLR via C#》非托管代码互操作性提到的P/Invoke Interop Assistant开发工具,包含源代码,是从网站:http://clrinterop.codeplex.com/ 上下载的,作为备份
JNA项目地址:https://jna.dev.java.net/ JNA使Java调用原生函数就像.NET上的P/Invoke一样方便、快捷。 JNA的功能和P/Invoke类似,但编写方法与P/Invoke截然不同。JNA没有使用Annotation, 而是通过编写一般的Java...
在.NET环境下编写与RS252串口通信的应用程序的唯一方法,就是引用过时了的并且有点限制的MSComm ActiveX控件。这篇文章介绍了用C#安全代码编写一个多线程的,且时尚的与RS232通讯的基础类库。这个类库使用平台调用...
利用P/Invoke获取更改后的系统CultureInfo
《精通.NET互操作:P/Invoke,C++ Interop和COM Interop》 因为此书太大,所以分为7部分下载;每部分可单独使用,为RAR压缩文件解压后为PDF版。因为第二部分和第六部分太大以后有权限了上传。 共分为:七部分 当前是:...
JNA的功能和P/Invoke类似,但编写方法与P/Invoke截然不同。JNA没有使用Annotation, 而是通过编写一般的Java代码来实现。 P/Invoke是.NET平台的机制。而JNA是Java平台上的一个开源类库,和其他类库没有 什么区别。只...
第5篇主要介绍了移动应用高级功能,如P/Invoke、POOM、资源与本地化、多线程编程、性能优化和移动安全策略等的开发。第6篇从实际... 目录 * 目录 序言 * 前言 第2章 .NET Compact Framework简介 * 2.1 概述 * 2.2...
C#串口操作小例子,P/Invoke 调用...........
http://blog.csdn.net/jasonliao909/article/details/50602528
源生成器,用于将用户定义的Win32 P / Invoke方法集和支持类型添加到C#项目中。 C#/ Win32 P / Invoke源生成器一个源生成器,用于将用户定义的Win32 P / Invoke方法集和支持类型添加到C#项目中。 功能快速向您的...
J/Invoke enables Java developers to easily invoke native methods (such as the Win32 API or C-based Windows DLLs and Unix dynamic libraries) with pure Java code. Unlike error-prone JNI programming ...
Ivoke-WCMDump 什么是Credential Manager ...从Credential Manager导出Windows凭据的Powershell脚本 https://github.com/peewpw/Invoke-WCMDump PS>Import-Module .\Invoke-WCMDump.ps1 ...Invoke-WCMDump