




已阅读5页,还剩12页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
NET互操作(一)平台调用前言:由于历史原因,.NET出现之前,开发人员已经编写了大量经历严格测试的非托管代码。它们以C库函数、C+类库已经COM组件的形式存在于应用程序和框架之中。但是,由于在非托管和托管对象模型之间,数据类型、方法签名和错误处理机制存在很大差异,从而是两种编程模型之间大门互用和移植便得很复杂。因此,在相当长的时间内,开发人员(尤其是PC软件开发人员),必须面对.NET与非托管代码长期共存的局面。在很多情况下,重用已有的非托管代码成了最有效、可行的解决方案。总论:NET提供3类主要互操作技术:l 平台调用技术(P/Invoke): 主要用于处理在托管代码中调用C库函数及Win32 API函数等非托管函数的情形。l C+ Interop: 适用于在托管代码与C+类库、核心算法库之间进行高效、灵活的互操作过程。一方面托管代码可以通过包装类机制使用C+类库,另一方面非托管代码可以通 过包装模板机制使用托管对象。l COM Interop:该技术用于处理托管代码与COM之间的交互过程。托管代码通过运行库可调用包装(RCW)使用非托管COM组件。反过来,非托管COM客 户端可以通过COM可调用包装(CCW)使用托管程序集。图1:NET提供3类主要互操作技术由于不同的非托管对象,其设计和运行机制等存在很多差异。因此,托管代码与这些非托管对象进行交互操作时,在数据类型处理、错误处理机制、创建和销毁对象 的规则以及互操作方法上,都需要根据不同的情况,分别进行不同的处理。平台调用一、 基础知识平台调用技术(P/Invoke): 主要用于处理在托管代码中调用C/C+库函数及Win32 API函数等非托管函数的情形一、基本要素一个简单例子:C+声明:extern C _declspec(dllexport) int Multiply(int factorA, int factorB);实现:int Multiply(int factorA, int factorB) return factorA * factorB;托管代码(C#)调用此非托管声明:class Invoker /声明非托管函数 DllImport(Interop.dll,EntryPoint = Multiply,CharSet = CharSet.Ansi) static extern int Multiply(int factorA, int factorB);总结: (声明托管函数)1.函数声明: extern修饰符和static修饰符2.DllImport属性 (常用) 指定动态库:指明平台要调用的dll名称,此项不可缺少。 指定入口点:EntryPoint 字段按名称或序号指定 DLL 函数,可以使用与原dll中不同名称,不填此项,默认为跟原函数名称一致。 指定字符集:CharSet 字段控制字符串封送处理并确定平台调用在 DLL 中查找函数名的方式。有窄版本 (ANSI) 和宽版本 (Unicode)。MSDN如下说明:成员名称说明Ansi以多字节字符串的形式封送字符串。Auto针对目标操作系统适当地自动封送字符串。在 Windows NT、Windows 2000、Windows XP 和 Windows Server 2003 系列上默认值为 Unicode;在 Windows 98 和 Windows Me 上默认值为 Ansi。尽管公共语言运行库默认值为 Auto,使用语言可重写此默认值。例如,默认情况下,C# 将所有方法和类型都标记为 Ansi。None此值已过时,它与 CharSet.Ansi 具有相同的行为。Unicode以 Unicode 2 字节字符形式封送字符串。指定调用约定:CallingConvention字 段指定调用在非托管代码中实现的方法所需的调用约定。动态链接库导出的一般有两种调用协议,_stdcall和_cdecl。_cdecl是 C/C+和MFC程序默认使用的调用约定:采用_cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。因 此,实现可变参数的函数只能使用该调用约定。_stdcall调用约定用于调用Win32 API函数。采用_stdcal约定时,函数参数按照从右 到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。MSDN如下说明:成员名称说明Cdecl调用方清理堆栈。这使您能够调用具有 varargs 的函数(如 Printf),使之可用于接受可变数目的参数的方法。FastCall不支持此调用约定。StdCall被调用方清理堆栈。这是使用平台 invoke 调用非托管函数的默认约定。ThisCall第一个参数是 this 指针,它存储在寄存器 ECX 中。其他参数被推送到堆栈上。此调用约定用于对从非托管 DLL 导出的类调用方法。Winapi此成员实际上不是调用约定,而是使用了默认平台调用约定。例如,在 Windows 上默认为 StdCall,在 Windows CE.NET 上默认为 Cdecl。3、注意事项: 参 数个数、顺序、类型必须保持“等价”一致,函数名称和参数名称可以不一致。二、 参数封送:字符串可以说新手使用P-INVOKE最开始的头疼就是C#和C+的字符串传递,由于不同编程语言对字符串处理的机制不同,因此导致托管代码的平台调用必须对字符串进行特殊的封送处理。本节将阐述以下几个问题:(1)、C#的string和C+的字符串首指针如何对应(2)、字符串还有ANSI和UNICODE(宽字符串)之分(3)、封送字符串数组1、通过CharSet字段控制字符串封送行为:C+:void _cdecl TestString1(char*hello);void _cdecl TestString2(const wchar_t* str,wchar_t* outStr,int size);MSDN上给出C/C+字符串类型与C#字符串类型的对应关系Wtypes.h 中类型 非托管C/C+ 托管类名 说明 CHAR char System.Char 用 ANSI 修饰。LPSTR char* System.String 或 System.Text.StringBuilder用 ANSI 修饰。LPCSTR Const char* System.String 或 System.Text.StringBuilder用 ANSI 修饰。LPWSTR wchar_t* System.String 或 System.Text.StringBuilder用 Unicode 修饰。LPCWSTR Const wchar_t* System.String 或 System.Text.StringBuilder用 Unicode 修饰。C#DllImport(test.dll, EntryPoint = TestString1, CharSet =CharSet.Ansi)public static extern voidTestString1(stringhello);DllImport(test.dll, EntryPoint = TestString1, CharSet =CharSet.Unicode)public static extern voidTestString2(stringstr,StringBuilder outStr,int size)2、使用MarshalAs属性控制字符串封送行为: CharSet字段影响的是整个函数过程的字符串封送行为,MarshalAs属性只影响其作用的字符串参数。因此,当一个非托管函数的参数即由ANSI字符串,又有Unicode字符串时,就只能用MarshalAs属性来控制封送行为。C+:void _cdecl TestString3(const char* str1,const wchar_t* str2,wchar_t* outStr,int size);MSDN给出MarshalAs属性控制字符串封送行为:枚举类型非托管格式说明UnmanagedType.AnsiBStr长度前缀为双字节的 Unicode字符的COM样式的BSTR。UnmanagedType.LPStr单字节、null空终止的 ANSI 字符数组的指针。(默认值)UnmanagedType.LPTStrnull空终止与平台相关的字符数组的指针。UnmanagedType.LPWStrnull空终止与Unicode的字符数组的指针。UnmanagedType.TBStr一个有长度前缀的与平台相关的 COM样式的BSTR。 需要注意的是:此表只适用于string类型,对于StringBuilder而言,能够允许的选项只有:LPStr、LPTStr、LPWStr。C#:DllImport(test.dll, EntryPoint = TestString3)public static extern voidTestString3(MarshalAs(UnmanagedType.LPStr)string str1,MarshalAs(UnmanagedType.LPWStr)string str2,MarshalAs(UnmanagedType.LPWStr)stringoutStr,int size);3、封送作为返回值的字符串:C+:char* _cdeclGetStringReturn1();wchar_t*_cdeclGetStringReturn2();这里,有两种声明方法:(1)、直接用string类型对应:DllImport(test.dll, EntryPoint = GetStringReturn1, CharSet = CharSet.Ansi)public static externstringGetStringReturn1();DllImport(test.dll, EntryPoint = GetStringReturn2, CharSet = CharSet.Unicode)public static externstring GetStringReturn2();(2)、用IntPtr指针对应:DllImport(test.dll, EntryPoint = GetStringReturn1, CharSet = CharSet.Ansi)public static externIntPtrGetStringReturn1();DllImport(test.dll, EntryPoint = GetStringReturn2, CharSet = CharSet.Unicode)public static externIntPtrGetStringReturn2();以GetStringReturn2为例,给出C#如何使用:string ret=;IntPtr strPtr=GetStringReturn2();ret=Marshal.PtrToStringUni(strPtr);/对于IntPtr传递的变量,需要手工释放非托管内存Marshal.FreeCoTaskMem(strPtr); /释放非托管内存是互操作的一个难题,将在后面的章节做专门的介绍4、封送字符串数组C+:int TestArrayOfStrings(char* ppStrArray, int size);C#: DllImport( test.dll ) public static extern int TestArrayOfStrings( In, Out String ppStrArray, int size );使用:String strArray = one, two, three, four, five ;int lenSum = LibWrap.TestArrayOfStrings( strArray, strArray.Length )三、 参数封送:结构体平时,我们接触的平台调用,对于简单的类型,一般很容易学会。因为简单类型有直观的类型对应。而结构体,是一种自定义类型,结构体成员可能会很复杂。所以,封送结构体变量,是平台调用的一个重点,也是个难点。本节篇幅较多,将阐述如下几个内容:(1)、结构体(指针)作为输入输出参数。(2)、结构体(指针)作为函数返回值。(3)、结构体中值类型数组。(4)、结构体中的字符指针和字符数组(5)、嵌套结构体(6)、结构体数组1、作为输入输出参数C+:typedef struct _MYPERSONchar* first; /字符指针char* last; MYPERSON, *LP_MYPERSON;typedef struct _MYPERSON1char first20;/字符数组char last20; MYPERSON1, *LP_MYPERSON1;typedef struct _MYARRAYSTRUCTbool flag;int vals 3 ; /值类型数组 MYARRAYSTRUCT;int TestStructInStruct1(MYPERSON pPerson);int TestStructInStruct2(MYPERSON* pPerson);int TestStructInStruct3(MYPERSON1* pPerson);void TestArrayInStruct( MYARRAYSTRUCT* pStruct );C#: StructLayout( LayoutKind.Sequential, CharSet=CharSet.Ansi )public struct MyPerson public String first;public String last; StructLayout( LayoutKind.Sequential, CharSet=CharSet.Ansi )public struct MyPerson1 MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)public String first;MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)public String last;public struct MyArrayStruct public bool flag; MarshalAs( UnmanagedType.ByValArray, SizeConst=3 )public int vals; DllImport( test.dll ,CharSet = CharSet.Ansi)public static extern int TestStructInStruct( MyPerson person); DllImport( test.dll ,CharSet = CharSet.Ansi)public static extern int TestStructInStruct1(ref MyPerson person); DllImport( test.dll ,CharSet = CharSet.Ansi)public static extern int TestStructInStruct2(ref MyPerson1 person); DllImport( test.dll ,CharSet = CharSet.Ansi)public static extern intTestArrayInStruct(ref MYARRAYSTRUCT person);总结:(1)、结构体声明必须保证:字段声明顺序、字段类型、字段在内存中的大小原来的一致!结构体名称,其成员名称可以不同。(2)、结构体中,char*与char在C#声明区别很大,前者直接对应string,后者(字符数组)很容易被初学者误用char来对应,它还是要用string来对应,但还需要用MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)来指明该字段的封送行为。(3)、其他值类型的数组,直接用数组方式对应,但也需要用 MarshalAs( UnmanagedType.ByValArray, SizeConst=3 ) 指明封送行为。 (4)、有直接结构体对应的结构体指针,建议直接用ref + 具体类型,而不采用IntPtr,省去一些不必要的转换操作,TestArrayInStruct、TestStructInStruct2、TestStructInStruct3都是如此。2、作为函数返回值:C+:MYPERSON* TestReturnStruct();void FreeStruct(MYPERSON* pStruct);C#: DllImport( test.dll ,CharSet = CharSet.Ansi)public static externIntPtrTestReturnStruct(); /注意对应的是IntPtr指针 DllImport( test.dll ,CharSet = CharSet.Ansi)public static externvoid FreeStruct(IntPtrpStruct);使用:IntPtrpStruct=TestReturnStruct();MYPERSON person=(MYPERSON)Marshal.PtrToStructure(pStruct,typeof(MYPERSON);/在非托管代码,大多用new/malloc分配内存,net无法正确释放,/需要对应的调用释放内存的方法释放非托管内存FreeStruct(pStruct);3、结构体嵌套结构体:C+:typedef struct _MYPERSON2MYPERSON* person;int age; MYPERSON2, *LP_MYPERSON2;typedef struct _MYPERSON3MYPERSON person; int age; MYPERSON3;int TestStructInStruct(MYPERSON2* pPerson2);void TestStructInStruct3(MYPERSON3 person3);C#: StructLayout( LayoutKind.Sequential )public struct MyPerson2 public IntPtr person;public int age; StructLayout( LayoutKind.Sequential )public struct MyPerson3 public MyPerson person;public int age; DllImport( test.dll )public static extern int TestStructInStruct( ref MyPerson2 person2 ); DllImport( test.dll )public static extern int TestStructInStruct3( MyPerson3 person3 );使用:MyPerson personName;personName.first = Mark;personName.last = Lee;MyPerson2 personAll;personAll.age = 30;IntPtr buffer = Marshal.AllocCoTaskMem( Marshal.SizeOf( personName );Marshal.StructureToPtr( personName, buffer, false );personAll.person = buffer;int res = TestStructInStruct( ref personAll );MyPerson personRes = (MyPerson)Marshal.PtrToStructure( personAll.person, typeof( MyPerson );Marshal.FreeCoTaskMem( buffer );MyPerson3 person3 = new MyPerson3();person3.person.first = John;person3.person.last = Evens;person3.age = 27;TestStructInStruct3( person3 );总结:(1)、结构体嵌套,如果是实体成员,直接用结构体类型对应,如上面的MyPerson3;(2)、如果是指针变量,则用IntPtr对应,如上面的MYPERSON2;(3)、如果嵌套的是结构体数组,那么,出来办法以值类型数组方式对应,如MYARRAYSTRUCT,只不过,类型为具体的结构体类型。这里不另外在举例。(还是给个例子)C+: typedefstructStudent charname20; intage; doublescores32; Student; typedefstructClass intnumber; Studentstudents126; Class; C#: 1. StructLayout(LayoutKind.Sequential) 2. public structStudent 3. 4. MarshalAs(UnmanagedType.ByValTStr,SizeConst=20) 5. publicstringname; 6. publicintage; 7. MarshalAs(UnmanagedType.ByValArray,SizeConst=32) 8. publicdoublescores; 9. 10. StructLayout(LayoutKind.Sequential) 11. structClass 12. 13. publicintnumber; 14. MarshalAs(UnmanagedType.ByValArray,SizeConst=126) 15. publicStudentstudents; 16. 17. 4、结构体数组作为输入输出参数:C+:int TestArrayOfStructs2 (MYPERSON* pPersonArray, int size);C#: DllImport( test.dll )public static extern int TestArrayOfStructs2( In, Out MyPerson personArray, int size );使用:MyPerson persons = new MyPerson( Kim, Akers ), new MyPerson( Adam, Barr );int namesSum = TestArrayOfStructs2( persons, persons.Length );总结:(1)、一般我们数组作为输入输出参数,需要显式加上In,Out属性,标识为输入输入参数。如果不写,默认为In方向,CLR将不会回传修改后的内存值。四、 参数封送:含有二维数组的结构体对于结构体二维数组,看似简单,其实很复杂。很多人往往不知从何下手,在托管和非托管代码之间总是不能正确传递值。先用一个例子:struct Lable1 BYTELabFilterChan04256; BYTELabFilterChan14256; 这是曾经有人这样在C进行定义的:第一个:StructLayout(LayoutKind.Sequential) public class Label1 public byte,LabFilterChan0 = new byte4, 256; public byte,LabFilterChan1 = new byte4, 256; 第二个:public struct Label1 byte,LabFilterChan0 = newbyte4,256; byte,LabFilterChan1 = new byte4,256;第三个:public struct Label1 byte4,256LabFilterChan0 ; byte4,256LabFilterChan1 ;咋一看,好像没什么问题!大家可以仔细看看,其实都有问题:对于非托管和托管的结构体对应,关键就是两点:1、两边的结构体大小要一致。2、字节顺序要一致!3、字符集。其实第二点主要针对嵌套联合体来说的,没有嵌套联合体,一 般按顺序字节方式即可:StructLayout(LayoutKind.Sequential)。对应嵌套联合体的结构体,将在下一篇进行讲解。 字符集比较好理解,就是Ansi和Unicode两种,即1个和2个字节。由于这个结构体没有包含字符串成员变量,所以,我们这里重点关注第一点!对于简单类型的成员变量,如byte、short
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《口语交际:即兴发言》教学设计 2024-2025学年语文六年级下册统编版
- 2025年全国汽车修理工(高级)职业技能考试复习题库【附答案】
- 第三单元第14课《电子商务》说课稿 2024-2025学年青岛版(2019)初中信息技术第一册
- 第二课 经济全球化说课稿-2025-2026学年初中历史与社会人教版2013九年级下册-人教版(新课程标准)
- 蒸腾作用课件
- 物流运输实务(第三版)习题及答案 项目二同步测试
- 2025年北京pcr考试题及答案
- 蒲柳人家课件观看
- 葡萄酒知识培训课件
- 2025劳动合同韩语模板
- 项目经理考核试题及答案
- 车载信息娱乐系统的设计与开发-全面剖析
- 安检岗位培训课件模板
- 2025-2030中国水产饲料原料和产品行业市场现状供需分析及投资评估规划分析研究报告
- 腹膜透析换液操作医学
- 静电检测专业知识培训课件
- 现代农业园区-规划设计方案
- 安全文明施工和质量管理制度
- 新媒体运营口薪酬考核制度150215
- 舞蹈兴趣小组教案
- 2024年湖南益阳市安化县医疗卫生单位招聘考试真题
评论
0/150
提交评论