一:入门,直接从 C# 调用 DLL 导出
其实我们的议题应该叫做C#如何直接调用非托管代码,通常有2种方法:
1. 直接调用从 DLL 导出的函数。
2. 调用 COM 对象上的接口方法
我主要讨论从dll中导出函数,基本步骤如下:
1.使用 C# 关键字 static 和 extern 声明方法。
2.将 DllImport 属性附加到该方法。DllImport 属性允许您指定包含该方法的 DLL 的名称。
3.如果需要,为方法的参数和返回值指定自定义封送处理信息,这将重写 .NET Framework 的默认封送处理。
好,我们开始
1.首先我们查询MSDN找到GetShortPathName的定义
The GetShortPathName function retrieves the
short path form of the specified path.
DWORD GetShortPathName(
LPCTSTR lpszLongPath,
LPTSTR lpszShortPath,
DWORD cchBuffer
);
2.查找对照表进行数据类型的转换(出处:[url]http://msdn.microsoft.com/msdnmag/issues/03/07/NET/default.aspx?fig=true[/url] )Data
Types
Win32 Types
|
Specification
|
CLR Type
|
char, INT8, SBYTE, CHARa€
|
8-bit signed integer
|
System.SByte
|
short, short int, INT16, SHORT
|
16-bit signed integer
|
System.Int16
|
int, long, long int, INT32, LONG32, BOOLa€ , INT
|
32-bit signed integer
|
System.Int32
|
__int64, INT64, LONGLONG
|
64-bit signed integer
|
System.Int64
|
unsigned char, UINT8, UCHARa€ , BYTE
|
8-bit unsigned integer
|
System.Byte
|
unsigned short, UINT16, USHORT, WORD, ATOM, WCHARa€ ,
__wchar_t
|
16-bit unsigned integer
|
System.UInt16
|
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD,
UINT
|
32-bit unsigned integer
|
System.UInt32
|
unsigned __int64, UINT64, DWORDLONG, ULONGLONG
|
64-bit unsigned integer
|
System.UInt64
|
float, FLOAT
|
Single-precision floating point
|
System.Single
|
double, long double, DOUBLE
|
Double-precision floating point
|
System.Double
|
a€ In Win32 this type is an integer with a specially assigned
meaning; in contrast, the CLR provides a specific type devoted to this
meaning.
|
3.调用GetShortPathName这个API,简单的写法如下(编译通过的话),
using System; using System.Runtime.InteropServices; public class MSSQL_ServerHandler { [DllImport("kernel32.dll")] public static extern int GetShortPathName ( string path, StringBuilder shortPath, int shortPathLength ) }
而我们之前的例子:
using System; using System.Runtime.InteropServices; public class MSSQL_ServerHandler { [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern int GetShortPathName ( [MarshalAs(UnmanagedType.LPTStr)] string path, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath, int shortPathLength ) }
对比可知,其中DllImport ,static,extern基本上是必须有的,其他CharSet,MarshalAs(…)是可选项,在这里即使没有,程序也是可以调用此API了。
说明:
1.MSSQL_ServerHandler. GetShortPathName 方法用 static 和 extern 修饰符声明并且具有 DllImport 属性,该属性使用默认名称GetShortPathName 通知编译器此实现来自kernel32.dll。若要对 C# 方法使用不同的名称(如getShort),则必须在 DllImport 属性中使用 EntryPoint 选项,如下所示:
[DllImport("kernel32.dll", EntryPoint="getShort")]
2.使用MarshalAs(UnmanagedType.LPTStr)保证了在任何平台上都会得到LPTStr,否则默认的方式会把从C#中的字符串作为BStr传递。
现在如果是仅含有简单参数和返回值的WIN32 API,就都可以利用这种方法进行对照,简单的改写和调用了。
二.背后的原理 ―― 知其所以然,相关的知识
1.平台调用详原理
平台调用依赖于元数据在运行时查找导出的函数并封送其参数。下图显示了这一过程。
对非托管 DLL 函数的“平台调用”调用
当“平台调用”调用非托管函数时,它将依次执行以下操作:
查找包含该函数的 DLL。
将该 DLL 加载到内存中。
查找函数在内存中的地址并将其参数推到堆栈上,以封送所需的数据。
注意 只在第一次调用函数时,才会查找和加载 DLL 并查找函数在内存中的地址。
将控制权转移给非托管函数。
平台调用会向托管调用方引发由非托管函数生成的异常。
2.关于Attribute(属性,注意蓝色字)
属性可以放置在几乎所有声明中(但特定的属性可能限制它在其上有效的声明类型)。在语法上,属性的指定方法为:将括在方括号中的属性名置于其适用的实体声明之前。例如,具有 DllImport 属性的类将声明如下:
[DllImport] public class MyDllimportClass { ... }
有关更多信息,请参见 DllImportAttribute 类。
许多属性都带参数,而这些参数可以是定位(未命名)参数也可以是命名参数。任何定位参数都必须按特定顺序指定并且不能省略,而命名参数是可选的且可以按任意顺序指定。首先指定定位参数。例如,这三个属性是等效的:
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
[DllImport("user32.dll")]
第一个参数(DLL 名称)是定位参数并且总是第一个出现,其他参数为命名参数。在此例中,两个命名参数都默认为假,因此它们可以省略(有关默认参数值的信息,请参见各个属性的文档)。
在一个声明中可以放置多个属性,可分开放置,也可放在同一组括号中:
bool AMethod([In][Out]ref double x);
bool AMethod([Out][In]ref double x);
bool AMethod([In,Out]ref double x);
某些属性对于给定实体可以指定多次。此类可多次使用的属性的一个示例是 Conditional:
[Conditional("DEBUG"), Conditional("TEST1")] void TraceMethod() {...}
注意 根据约定,所有属性名称都以单词“Attribute”结束,以便将它们与 .NET
Framework 中的其他项区分。但是,在代码中使用属性时不需要指定属性后缀。例如,
[DllImport]
虽等效于 [DllImportAttribute]
,但 DllImportAttribute 才是该属性在 .NET
Framework 中的实际名称。
3.MarshalAsAttribute 类
指示如何在托管代码和非托管代码之间封送数据。可将该属性应用于参数、字段或返回值。
该属性为可选属性,因为每个数据类型都有默认的封送处理行为。
大多数情况下,该属性只是使用 UnmanagedType 枚举标识非托管数据的格式。
例如,默认情况下,公共语言运行库将字符串参数作为 BStr 封送到 COM 方法,但是可以通过制定MarshalAs属性,将字符串作为 LPStr、LPWStr、LPTStr 或 BStr 封送到非托管代码。某些 UnmanagedType 枚举成员需要附加信息。
三:进阶,如何处理含有复杂的参数和返回值类型的API的调用(To Be Continue…)
参考文章
1. Eric Gunnerson的《使用 Win32 和其他库》(非常好!!!)
2. MSDN:C# 程序员参考 平台调用教程 。
本文出自 “锉人Kris” 博客,请务必保留此出处http://chris.blog.51cto.com/112473/29285