其实我们的议题应该叫做
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, CHARâ€
|
8-bit signed integer
|
System.SByte
|
short, short int, INT16, SHORT
|
16-bit signed integer
|
System.Int16
|
int, long, long int, INT32, LONG32, BOOL†, INT
|
32-bit signed integer
|
System.Int32
|
__int64, INT64, LONGLONG
|
64-bit signed integer
|
System.Int64
|
unsigned char, UINT8, UCHAR†, BYTE
|
8-bit unsigned integer
|
System.Byte
|
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR†, __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
|
†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
中的实际名称。
指示如何在托管代码和非托管代码之间封送数据。可将该属性应用于参数、字段或返回值。
该属性为可选属性,因为每个数据类型都有默认的封送处理行为。
大多数情况下,该属性只是使用
UnmanagedType 枚举标识非托管数据的格式。
例如,默认情况下,公共语言运行库将字符串参数作为
BStr封送到 COM 方法,但是可以通过制定MarshalAs属性,将字符串作为
LPStr、
LPWStr、
LPTStr 或
BStr 封送到非托管代码。某些
UnmanagedType枚举成员需要附加信息。
三:进阶,如何处理含有复杂的参数和返回值类型的API的调用(To Be Continue…)
参考文章
1.
Eric Gunnerson的《
使用 Win32 和其他库》(非常好!!!)
2.
MSDN:
C# 程序员参考平台调用教程 。
3.
《在 C# 中通过 P/Invoke 调用Win32 DLL》Jason Clark