P/Invoke应用

转自:http://www.cnblogs.com/hanginthere/archive/2011/08/03/2126422.html

P/Invoke应用备忘

P/Invoke -- Platform Invoke:提供了一种从托管代码访问并调用非托管代码的方法,应用场景包括从托管代码直接调用Win32 API或其他一些非托管代码实现的库等。

在最近一个蓝牙通信相关的小项目中,需要用到第三方提供的由C语言编写的底层通信API,并希望用C#和Winform快速完成界面开发,P/Invoke正好可以满足需要。

使用P/Invoke调用非托管代码的前提和主要工作就是确保托管/非托管代码之间正确的映射,包括:

1. 为使用的每个方法提供正确的声明;

2. 完成方法参数、返回值的正确映射,包括基本类型、结构体、指针(函数指针)等;

方法声明

P/Invoke要求方法须声明为static和extern的,static是因为非托管代码可能没有"对象实例"的概念,extern则是要让编译器知道这是一个DLL导入的方法,不需要在这里提供方法实现。在方法声明处,还要附上"DllImport"属性(来自System.Runtime.InteropServices.DllImportAttribute),该属性标明所引用的DLL名称,如下例:

原版声明:long MyNaiveFunc();  //位于GoodAPIs.dll

托管声明:[DllImport("GoodAPIs.dll")]

                 static extern int MyNaiveFunc();

这样声明之后,托管代码就可以像使用其他方法一样使用这个API了。

DllImport属性还支持一些参数,以常用的"EntryPoint"和"SetLastError"为例:

              [DllImport("GoodAPIs.dll", EntryPoint="MyNaiveFunc", SetLastError=true)]

              static extern int MyFancyFunc();

默认情况下,声明的方法名称须和DLL中的目标方法同名,以便P/Invoke能找到正确的方法地址,但使用"EntryPoint"参数指定目标方法名称后,我们就可以自定义对应的托管名称了;"SetLastError"在调用Win32 API时尤其有用,它通知CLR在每次执行该调用时保存API设置的错误值,随后,我们可以通过Marshal类(同样来自System.Runtime.InteropServices)的GetLastWin32Error()方法获得可能的错误信息。(GetLastWin32Error()正是对kernal32.dll中的GetLastError()的封装)


类型映射

1 基本类型映射

非托管代码中的类型和.NET中支持的类型大致有如下的映射关系:

C/C++C#
HANDLE, LPDWORD, LPVOID, void*IntPtr
LPCTSTR, LPCTSTR, LPSTR, char*, const char*, Wchar_t*, LPWSTRString [in], StringBuilder [in, out]
DWORD, unsigned long, UlongUInt32, [MarshalAs(UnmanagedType.U4)]
boolbool
LP<struct>[In] ref <struct>
SIZE_Tuint
LPDWORDout uint
LPTSTR[Out] StringBuilder
PULARGE_INTEGERout ulong
WORDUint16
Byte, unsigned charbyte
ShortInt16
Long, intInt32
floatsingle
doubledouble
NULL pointerIntPtr.Zero
UintUint32

如第一个例子中,C方法返回值的类型是long,而在托管代码中,则对应int(即System.Int32)。

2 结构体

C/C++代码中存在大量结构体定义,要在托管代码中使用它们,也需要进行声明,例如:

原版声明:typedef struct MyNaiveStru{

                       unsigned char uc;

         unsigned long ul;

                       char chArr[32];

                  }

托管声明:[StructLayout(LayoutKind.Sequential)]

                 public struct MyFancyStru{

                       public byte uc;

                       public int ul;

                       [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]

                       public char[] chArr;

                   }

有几点需要说明:首先,用class和struct声明都可以;第二,声明时要附加StructLayout属性,将结构体的内存布局设置为"顺序的",这是因为如果不设置,CLR就可能为优化内存使用而打乱结构体中成员的位置,这将导致与非托管代码交互时的混乱;第三,MarshalAs属性的作用是让我们自定义类型的映射关系,在这里为了正确将c#定义的数组映射为中的char[32],我们将它映射为UnmanagedType.ByValArray,这时,需用SizeConst参数设置数组大小。

3 指针

在上面的映射表中,已经给出了一些指针的映射关系,实际上在很多情况下,可以直接使用C#的ref/out来处理指针的情况,举例说明(略去DllImport):

原版声明:void MyNaiveFunc(unsigned long *pul);

托管声明:static extern MyNaiveFunc(ref uint pul);

对于在方法中赋值并传出的字符串,可如下例处理:

原版声明:int GetStrByPara(char *str);

托管声明:int GetStrByPara([Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder str);

对于结构体指针,可如下处理:

原版声明:void WorkWithStru(MyStru *pStru);

托管声明:void WorkWithStru(ref MyStru pStru);

或:             void WorkWithStru(IntPtr pStru);   //这个方法是开始时用的,现在看来其实没有必要,还是记录一下;

其中pStru定义为:

                 MyStru stru = new MyStru();

                 IntPtr pStru = Marshal.AllocHGlobal(Marshal.SizeOf(MyStru));

                 Marshal.StructureToPtr(stru, pStru, false);

                 //use it...

                 Marshal.FreeHGlobal(pStru);

当声明为结构体指针的参数实际想要传出的是结构体数组时,甚至可以这样:

原版声明:void GetStruArray(MyStru *pStrus);

托管声明:void GetStruArray(MyStru[] pStrus);

另一种常用的类型是函数指针,我们可以直接定义相同(映射)签名的delegate来进行对应:

原版声明:typedef void (MyCallbackFunc)(unsgined int hdl, unsigned char *param);

                 void WorkWithCBK(MyCallbackFunc *cbk);

托管声明:public delegate void MyCallbackFunc(uint hdl, IntPtr param); //param在我实际的case中虽然声明为unsigned char *,但它其实可以转化为指向多种类型的指针,所以这里用IntPtr,在callback内部,根据情况可使用Marshal.PtrToStructure()进行转换..

          void WorkWithCBK(MyCallbackFunc cbk);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值