引言
这几天在做一个应用的时候,遇到用C#调用c++编译的动态链接库文件中函数的问题。于是研究了一下.net framework内托管代码和非托管代码之间的相互调用问题。在msdn中对托管代码的定义为[1]:在运行时控制下执行的代码。反之则为非托管代码。非托管代码主要有com组件,ActiveX,Win32API以及各种在.net framework之外编译的二进制代码。采用托管代码和.net framework进行开发具有很多优点,但是很多遗留的代码不是基于.net framework的,这些代码经过历史的积累,证明了他们的正确性,是一笔很大的资源,如何将这些资源和.net framework托管代码整合,从而利用.net framework开发的简便性和原有代码的功能是一个值得探讨的问题。
在这篇文章中,我主要讨论托管代码和DLL中导出函数的交互。托管代码与com组件交互问题在下一篇给出。
托管代码与dll函数的交互
交互图解
这类互操作问题主要通过.net平台提供的互操作服务实现。当托管源代码中调用位于dll文件里面的非托管函数时, 并不是真正把dll的函数编译进去或者直接引用非托管函数。非托管代码将请求发送给.net运行时,.net加载dll文件,然后将参数做适当的封装,发送到非托管代码中,将返回值再做一次封装,返回给调用对象。就完成一次交互操作。
使用导出的 DLL 函数
-
标识 DLL 中的函数。
最低限度上,必须指定函数的名称和包含该函数的 DLL 的名称。
-
创建用于容纳 DLL 函数的类。
可以使用现有类,为每一非托管函数创建单独的类,或者创建包含一组相关的非托管函数的一个类。
-
在托管代码中创建原型。
[Visual Basic] 使用带 Function 和 Lib 关键字的 Declare 语句。在某些少见的情况下,可以使用带 Shared Function 关键字的 DllImportAttribute。这些情况在本节后面部分进行说明。
[C#] 使用 DllImportAttribute 标识 DLL 和函数。用 static 和 extern 修饰符标记方法。
[C++] 使用 DllImportAttribute 标识 DLL 和函数。用 extern "C" 标记包装方法或函数。
-
调用 DLL 函数。
像处理其他任何托管方法一样调用托管类上的方法。
使用DllImport 和 DllImportAttribute声明函数原型
以user32.dll中导出MessageBox为例:
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
public static extern IntPtr MessageBox(int hWnd, String text, String caption, uint type);
调用函数
因为不同语言中的数据类型不同,因此调用dll中的函数时,要注意托管代码内的数据类型到非托管代码所使用的数据类型之间的对应关系,也就是传递和接收参数时,对数据类型做适当的包装。下表列出了在 Win32 API(在 Wtypes.h 中列出)和 C 样式函数中使用的数据类型。许多非托管库包含将这些数据类型作为参数传递并返回值的函数。第三列列出了在托管代码中使用的相应的 .NET Framework 内置值类型或类。
Wtypes.h 中的非托管类型
非托管 C 语言类型
托管类名
说明
HANDLE
void*
在 32 位 Windows 操作系统上为 32 位,在 64 位 Windows 操作系统上为 64 位。
BYTE
unsigned char
8 位
SHORT
short
16 位
WORD
unsigned short
16 位
INT
int
32 位
UINT
unsigned int
32 位
LONG
long
32 位
BOOL
long
32 位
DWORD
unsigned long
32 位
ULONG
unsigned long
32 位
CHAR
char
用 ANSI 修饰。
LPSTR
char*
用 ANSI 修饰。
LPCSTR
Const char*
用 ANSI 修饰。
LPWSTR
wchar_t*
用 Unicode 修饰。
LPCWSTR
Const wchar_t*
用 Unicode 修饰。
FLOAT
Float
32 位
DOUBLE
Double
64 位
平台调用复制字符串参数,并在必要时将其从 .NET Framework 格式 (Unicode) 转换为非托管格式 (ANSI)。由于托管字符串是不可变的,因此当函数返回时,平台调用不会将它们从非托管内存复制回托管内存。
下表列出了字符串的封送处理选项,描述了它们的用法,并提供了到相应的 .NET Framework 示例的链接。
字符串
说明
示例
通过值传递。
将字符串作为 In 参数传递。
作为结果。
从非托管代码返回字符串。
通过引用传递。
使用 StringBuilder 将字符串作为 In/Out 参数传递。
在结构中通过值传递。
在作为 In 参数的结构中传递字符串。
在结构中通过引用传递 (char*)。
在作为 In/Out 参数的结构中传递字符串。非托管函数需要指向字符缓冲区的指针,并且缓冲区大小是结构的成员。
在结构中通过引用传递 (char[])。
在作为 In/Out 参数的结构中传递字符串。非托管函数需要嵌入的字符缓冲区。
在类中通过值传递 (char*)。
在作为 In/Out 参数的类中传递字符串。非托管函数需要指向字符缓冲区的指针。
在类中通过值传递 (char[])。
在作为 In/Out 参数的类中传递字符串。非托管函数需要嵌入的字符缓冲区。
作为通过值传递的字符串数组。
创建通过值传递的字符串数组。
作为包含通过值传递的字符串的结构数组。
创建包含字符串的结构数组,并且该数组是通过值传递的。
在托管代码中,数组是包含一个或多个相同类型元素的引用类型。虽然数组是引用类型,但它们被作为 In 参数传递给非托管函数。此行为与将托管数组作为 In/Out 参数传递给托管对象的方式是不一致的。
数组
说明
通过值传递的整数数组。
将整数数组作为 In 参数传递。
通过引用传递的整数数组。
将整数数组作为 In/Out 参数传递。
通过值传递的整数数组(二维)。
将整数矩阵作为 In 参数传递。
通过值传递的字符串数组。
将字符串数组作为 In 参数传递。
包含整数的结构数组。
将包含整数的结构数组作为 In 参数传递。
包含字符串的结构数组。
传递只包含将整数作为 In/Out 参数的结构的数组。可以更改数组的成员。
类和结构在 .NET Framework 中是类似的。它们都可以具有字段、属性和事件。它们也有静态和非静态方法。一个显著区别是结构属于值类型而类属于引用类型。
下表列出类、结构和联合的封送处理选项;描述它们的用法;提供到相应的平台调用示例的链接。
类型
说明
示例
通过值传递的类。
将具有整数成员的类作为 In/Out 参数传递,与托管的情形相同。
通过值传递的结构。
将结构作为 In 参数传递。
通过引用传递的结构。
将结构作为 In/Out 参数传递。
具有嵌套结构的结构(单一化)。
传递在非托管函数中表示具有嵌套结构的结构的类。该结构在托管原型中被单一化为一个大结构。
具有嵌套结构的结构(未单一化)。
传递具有嵌入结构的结构。
具有指向其他结构的指针的结构。
传递包含指向另一个结构的指针作为成员的结构。
具有通过值传递的整数的结构数组。
传递只包含将整数作为 In/Out 参数的结构的数组。可以更改数组的成员。
具有通过引用传递的整数和字符串的结构数组。
将包含整数和字符串的结构数组作为 Out 参数传递。被调用函数为该数组分配内存。
具有值类型的联合。
传递具有值类型(整型和双精度型)的联合。
具有混合类型的联合。
传递具有混合类型(整型和字符串类型)的联合。
结构中的空值。
传递空引用(在 Visual Basic 中为 Nothing),而不是对值类型的引用。
l 参考
与非托管代码交互操作 msdn http://msdn.microsoft.com/zh-cn/library/sd10k43k.aspx
从C#程序中调用非受管DLLs http://www.vckbase.com/document/viewdoc/?id=414