[CF Skills]P/invoke in .NET Compact Framework

概要
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 供调用:
 1 using  System.Runtime.InteropServices;
 2
 3 [DLLImport( " coredll.dll " , EntryPoint = "  GetAsyncKeyState " )]
 4 public   static   extern   short  GetMyKeyState ( int  nKey)
 5
 6
 7 // 调用时候如下:
 8 int  i  =   2 ;
 9 short  nkeyState  =  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 修饰的参数指定一个值,例如:        
1 double  a  =   4.0 ;
2 double  b  =   4.0 ;
3 myRef.DoubleMean2( ref  a, ref  b, out  mean);
4      public   class  myRef
5      {
6        [DllImport("nativetest1.dll")]
7        public static extern void DoubleMean2(ref double a,ref double b,out double mean);
8    }

9

这里的参数mean只是作为函数的 一个操作产物获取出来

下面来看看在本地代码中应当如何操作以供调用
简单的int型值传递:

 1 extern   " C "  __declspec(dllexport)  int  IntAdd( int  x,  int  y)
 2 {
 3   return (x+y);
 4}
 
 5 // 注意double型的参数为指针形式:
 6 extern   " C " __declspec(dllexport)
 7 void  DoubleMean2( double   * x,  double   * y,  double   * mean)
 8 {
 9    *mean = (*+ *y) / 2.0;
10}

11

 

在函数声明的前面,添加了一段 extern "C" __declspec (dllexport) ,作用是以 C 风格的形式将函数暴露以供外部调用
对于 constant 的常量值类型,在 C/C++ 中我们通常这样定义它们:
1 #define  SUNDAY      0
2 #define  MONDAY      1
3 #define  TUESDAY     2
4 #define  WEDNESDAY   3
5 #define  THURSDAY    4
6 #define  FRIDAY      5
7 #define  SATURDAY    6 
8

而在托管代码中我们直接使用const关键字修饰这种常量

1 const   int  SUNDAY     =   0 ;
2 const   int  MONDAY     =   1 ;
3 const   int  TUESDAY    =   2 ;
4 const   int  WEDNESDAY  =   3 ;
5 const   int  THURSDAY   =   4 ;
6 const   int  FRIDAY     =   5 ;
7 const   int  SATURDAY   =   6 ;

C#中也可以简单的定义在一个枚举类型里面:

 1 enum    Dates :  int
 2 {
 3    SUNDAY    = 0,
 4    MONDAY    = 1,
 5    TUESDAY   = 2,
 6    WEDNESDAY = 3,
 7    THURSDAY  = 4,
 8    FRIDAY    = 5,
 9    SATURDAY  = 6,
10}

11

前面都是讲的值类型的传递的例子,下面来看看引用类型是如何Marshal
1.     数组
方案:
C/C++参数:头指针,长度
C#参数:数组实例,长度
以下函数用来计算数组的平均值:

 1 extern   " C "  __declspec(dllexport)
 2 int  MeanArray( int   * pItem,  int  len)
 3 {
 4    if (len < 1)
 5       return -1// Empty
 6    int Sum = 0;
 7    for ( int i= 0; i < len; i++)
 8      Sum += pItem[i];
 9    return Sum / len;
10}

C#中调用如下:

[DllImport( " MarshalTypeDll.dll " )]
extern   static   int  MeanArray( int [] pItem,  int  len);
int [] stuScores  =   new   int []  78855192819665} ;
int  mean  =  MeanArray(stuScores, stuScores.Length);
MessageBox.Show(String.Format(
" The class average of final exam is {0} " , mean));

2.      字符串
    在Windows Mobile的系统下只支持 Unicode 的字符编码。
    方案:
    C/C++参数:WCHAR */CString
    C#参数:如果在本地代码中可能要变化字符串则用StringBuilder,静止的(unmutable)字符串值就直接用String传。

    可以使用 System.String System.Text.StringBuilder 类来传递 Unicode 字符序列给本地代码。.NET Compact Framework的运行时会自动附加上一个表示终结的字符’\0’到该字符序列的后面,这样就是我们熟知的C 风格的string了,值得一提的是在 .NET Compact Framework, System.String类型的对象从设计上采取了不可变模式, 也就是说它们在运行时值不可被改变. 如果程序运行的时候它们的数据需要被改变, 比如附加一个串到它结尾或者要移除某些字符,那么 .NET CLR 会为这样的操作产生一个新的对象拷贝. 所以你不能直接传入一个string给可能改变它的非托管代码。

3.      传结构和类
方案:使用 MarshalAsAtrribute

对于那些只含有简单又通用(通用指的是有相同内存存储结构)的数据类型,直接写就是了。但是很多情况下,我们遇到的都是一些包含复杂元素的数据类型。这时就需要用到MarshalAsAttribute UnmanagedType 枚举了.下面一行C#代码的作用是传递一个两个字节空截止(Null-terminated)Unicode 字符序列到非托管代码中:
void PInvokeAnCFunction([MarshalAs(UnmanagedType.LPWStr)] string s);

    再看一个稍微复杂点的
    在C++里面我们这样描述

     struct  ComplexStruct
    
{
        
int   intAry[10];
        
char  charAry[80];
        WCHAR  
*pStr;
    }
;

   看看我们在 C#中是如何定义这个结构的:

     struct  ComplexStruct
    
{
        [MarshalAs(UnmanagedType.ByValArray, sizeConst
=10)]  int[]  intAry;
        [MarshalAs(UnmanagedType.ByValTStr, sizeConst
=80)]  string str1;
        [marshalAs(UnmanagedType.LPWStr)] 
string str2;
    }
;

 

这里要注意当使用 ByValArray 或者 ByValTStr , 你必须指定 sizeConst 因为它们都是定长的数据类型。

 

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 in .NET Compact Framework
黄季冬<fox23> @ HUST All Rights Reserved
2008-1-11

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值