NET互操作 -

更新时间:2023-10-07 00:21:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

NET互操作(一) ——平台调用

前言:由于历史原因,.NET出现之前,开发人员已经编写了大量经历严格测试的非托管代码。它们以C库函数、C++类库已经COM组件的形式存在于应用程序和框架之中。但是,由于在非托管和托管对象模型之间,数据类型、方法签名和错误处理机制存在很大差异,从而是两种编程模型之间大门互用和移植便得很复杂。

因此,在相当长的时间内,开发人员(尤其是PC软件开发人员),必须面对.NET与非托管代码长期共存的局面。在很多情况下,重用已有的非托管代码成了最有效、可行的解决方案。

总论:NET提供3类主要互操作技术:

? 平台调用技术(P/Invoke): 主要用于处理在托管代码中调用C库函数及Win32 API函数等

非托管函数的情形。

? C++ Interop: 适用于在托管代码与C++类库、核心算法库之间进行高效、灵活的互操作过

程。一方面托管代码可以通过包装类机制使用C++类库,另一方面非托管代码可以通 过包装模板机制使用托管对象。

? COM Interop:该技术用于处理托管代码与COM之间的交互过程。托管代码通过运行库

可调用包装(RCW)使用非托管COM组件。反过来,非托管COM客 户端可以通过COM可调用包装(CCW)使用托管程序集。

托管环境平台调用技术P/InvokeC++ Interop托管包装类非托管环境C库函数Win32 API.NET应用程序C++类库核心算法库COM InteropRCW/CCWCOM组件

图1:NET提供3类主要互操作技术

由于不同的非托管对象,其设计和运行机制等存在很多差异。因此,托管代码与这些非托管对象进行交互操作时,在数据类型处理、错误处理机制、创建和销毁对象 的规则以及互操作方法上,都需要根据不同的情况,分别进行不同的处理。

平台调用

一、 基础知识

平台调用技术(P/Invoke): 主要用于处理在托管代码中调用C/C++库函数及Win32 API函数等非托管函数的情形 一、基本要素

一个简单例子: C++声明:

extern \实现:

int Multiply(int factorA, int factorB) {

return factorA * factorB; }

托管代码(C#)调用此非托管声明: class Invoker {

//声明非托管函数

[DllImport(\ 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。

此值已过时,它与 CharSet.Ansi 具有相同的行为。

None

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 中类型 CHAR LPSTR

非托管C/C++ char char*

托管类名 说明

用 ANSI 修饰。

System.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(\CharSet.Ansi)] public static extern voidTestString1(string hello);

[DllImport(\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.LPTStr null空终止与平台相关的字符数组的指针。 UnmanagedType.LPWStr null空终止与Unicode的字符数组的指针。 UnmanagedType.TBStr 一个有长度前缀的与平台相关的 COM样式的BSTR。

需要注意的是:此表只适用于string类型,对于StringBuilder而言,能够允许的选项只有:LPStr、LPTStr、LPWStr。 C#:

[DllImport(\public static extern voidTestString3( [MarshalAs(UnmanagedType.LPStr)]string str1, [MarshalAs(UnmanagedType.LPWStr)]string str2,

[MarshalAs(UnmanagedType.LPWStr)]stringoutStr,int size); 3、封送作为返回值的字符串: C++:

char* __cdecl GetStringReturn1(); wchar_t*__cdecl GetStringReturn2(); 这里,有两种声明方法: (1)、直接用string类型对应:

[DllImport(\CharSet.Ansi)] public static externstringGetStringReturn1();

[DllImport(\EntryPoint = \CharSet = CharSet.Unicode)] public static externstring GetStringReturn2(); (2)、用IntPtr指针对应:

[DllImport(\CharSet.Ansi)] public static externIntPtrGetStringReturn1();

[DllImport(\EntryPoint = \CharSet = CharSet.Unicode)]

本文来源:https://www.bwwdw.com/article/q37d.html

Top