动态加载
DLL
需要使用
Windows API
函数:
LoadLibrary
、
GetProcAddress
以及
FreeLibrary
。我们可以使用
DllImport
在
C#
中使用这三个函数。
[DllImport("Kernel32")]
public
static
extern
int
GetProcAddress(inthandle, Stringfuncname);
[DllImport("Kernel32")]
public
static
extern
int
LoadLibrary(Stringfuncname);
[DllImport("Kernel32")]
public
static
extern
int
FreeLibrary(inthandle);
当我们在
C++
中动态调用
Dll
中的函数时,我们一般的方法是:
假设
DLL
中有一个导出函数,函数原型如下:
BOOL
__stdcall foo(Object &object, LPVOID lpReserved);
1
、首先定义相应的函数指针:
typedef
BOOL (__stdcall *PFOO)(Object &object, LPVOID lpReserved);
2
、调用
LoadLibrary
加载
dll
:
HINSTANCE
hInst = ::LoadLibraryW(dllFileName);
3
、调用
GetProcAddress
函数获取要调用函数的地址:
PFOO
foo = (PFOO)GetProcAddress(hInst,"foo");
if
(foo == NULL)
{
FreeLibrary(hInst);
return false;
}
4
、调用
foo
函数:
BOOL
bRet = foo(object,(LPVOID)NULL);
5
、使用完后应释放
DLL
:
FreeLibrary(hInst);
那么在
C#
中应该怎么做呢?方法基本上一样,我们使用委托来代替
C++
的函数指针,通过
.NET Framework 2.0
新增的函数
GetDelegateForFunctionPointer
来得到一个委托的实例:
下面封装了一个类,通过该类我们就可以在
C#
中动态调用
Dll
中的函数了:
public class
DLLWrapper
{
///<summary>
/// API LoadLibrary
///</summary>
[DllImport("Kernel32")]
publicstaticexternintLoadLibrary(Stringfuncname);
///<summary>
/// API GetProcAddress
///</summary>
[DllImport("Kernel32")]
publicstaticexternintGetProcAddress(inthandle, Stringfuncname);
///<summary>
/// API FreeLibrary
///</summary>
[DllImport("Kernel32")]
publicstaticexternintFreeLibrary(inthandle);
///<summary>
///
通过非托管函数名转换为对应的委托
, by jingzhongrong
///</summary>
///<param name="dllModule">
通过
LoadLibrary
获得的
DLL
句柄
</param>
///<param name="functionName">
非托管函数名
</param>
///<param name="t">
对应的委托类型
</param>
///<returns>
委托实例,可强制转换为适当的委托类型
</returns>
publicstaticDelegateGetFunctionAddress(intdllModule, stringfunctionName, Typet)
{
intaddress = GetProcAddress(dllModule, functionName);
if (address == 0)
returnnull;
else
returnMarshal.GetDelegateForFunctionPointer(newIntPtr(address), t);
}
///<summary>
///
将表示函数地址的
IntPtr
实例转换成对应的委托
, by jingzhongrong
///</summary>
publicstaticDelegateGetDelegateFromIntPtr(IntPtraddress, Typet)
{
if (address == IntPtr.Zero)
returnnull;
else
returnMarshal.GetDelegateForFunctionPointer(address, t);
}
///<summary>
///
将表示函数地址的
int
转换成对应的委托,by jingzhongrong
///</summary>
publicstaticDelegateGetDelegateFromIntPtr(intaddress, Typet)
{
if (address == 0)
returnnull;
else
returnMarshal.GetDelegateForFunctionPointer(newIntPtr(address), t);
}
}
通过这个类,我们这样调用
DLL
:
1
、声明相应的委托(正确声明很重要,否则不能调用成功,后面有详细介绍)。
2
、加载
DLL
:
int
hModule
= DLLWrapper.LoadLibrary(dllFilePath);
if
(hModule == 0)
returnfalse;
3
、获取相应的委托实例:
FOO
foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO));
if
(foo == null)
{
DLLWrapper.FreeLibrary(hModule);
returnfalse;
}
4
、调用函数:
foo(...);
5
、
.NET
并不能自动释放动态加载的
DLL
,因此我们在使用完
DLL
后应该自己释放
DLL
:
DLLWrapper
.FreeLibrary(hModule);
下面我们将就委托应如何声明进行相应的讨论,在实际操作过程中,我发现使用
DllImport
方法和动态调用方法两者在
C#
中对
DLL
中函数原型的声明是有些区别的,下面我介绍动态调用中委托的声明:
1
、首先应该注意的是,
C++
中的类型和
C#
中类型的对应关系,比如
C++
中的
long
应该对应
C#
中的
Int32
而不是
long
,否则将导致调用结果出错。
2
、结构的声明使用
StructLayout对结构的相应布局进行设置,具体的请查看
MSDN:
使用
LayoutKind
指定结构中成员的布局顺序,一般可以使用
Sequential
:
[StructLayout(LayoutKind.Sequential)]
structStructVersionInfo
{
publicintMajorVersion;
publicintMinorVersion;
}
另外,如果单独使用内部类型没有另外使用到字符串、结构、类,可以将结构在
C#
中声明为
class
:
[StructLayout(LayoutKind.Sequential)]
classStructVersionInfo
{
publicintMajorVersion;
publicintMinorVersion;
}
对应
C++
中的声明:
typedef
struct
_VERSION_INFO
{
intMajorVersion;
intMinorVersion;
} VERSION_INFO, *PVERSION_INFO;
如果结构中使用到了字符串,最好应指定相应的字符集:
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
部分常用的声明对应关系(在结构中):
C++
:字符串数组
wchar_t
Comments[120];
C#
:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 120)]
publicstringComments;
C++
:结构成员
VERSION_INFO
ver;
C#
public
StructVersionInfo
ver;
C++
:函数指针声明
PFOO
pFoo; //
具体声明见文章前面部分
C#:
public
IntPtr
pFoo;
//
也可以为
public int pFoo;
//
不同的声明方法可以使用上面
DLLWrapper
类的相应函数获取对应的委托实例
如果在结构中使用到了
union
,那么可以使用
FieldOffset
指定具体位置。
3
、委托的声明:
当
C++
编写的
DLL
函数需要通过指针传出将一个结构:如以下声明:
void
getVersionInfo(
VERSION_INFO
*ver);
对于在
C#
中声明为
class
的结构(当
VERSION_INFO
声明为
class
)
delegate void
getVersionInfo
(
VERSION_INFO
ver);
如果结构声明为
struct
,那么应该使用如下声明:
delegate void
getVersionInfo
(
ref
VERSION_INFO
ver);
注意:应该使用
ref
关键字。
如果
DLL
函数需要传入一个字符串,比如这样:
BOOL
__stdcall jingzhongrong1(constwchar_t* lpFileName, int* FileNum);
那么使用委托来调用函数的时候应该在
C#
中如下声明委托:
delegatebooljingzhongrong1(
[MarshalAs(UnmanagedType.LPWStr)]StringFileName,
refintFileNum);
注意:应该使用
[MarshalAs(UnmanagedType.LPWStr)]
和
String
进行声明。
如果要在
DLL
函数中传出一个字符串,比如这样:
void
__stdcall jingzhongrong2(
wchar_t* lpFileName,
//
要传出的字符串
int* Length);
那么我们如下声明委托:
//
使用委托从非托管函数的参数中传出的字符串,
//
应该这样声明,并在调用前为
StringBuilder
预备足够的空间
delegatevoidjingzhongrong2(
[MarshalAs(UnmanagedType.LPWStr)] StringBuilderlpFileName,
refintLength,
);
在使用函数前,应先为
StringBuilder
声明足够的空间用于存放字符串:
StringBuilder
fileName = newStringBuilder(FileNameLength);