C#动态调用C++编写的DLL函数

C#动态调用C++编写的DLL函数

动态加载DLL需要使用Windows API函数:LoadLibrary、GetProcAddress以及FreeLibrary。我们可以使用DllImport在C#中使用这三个函数。 

[DllImport("Kernel32")] 
public static extern int GetProcAddress(int handle, String funcname); 

[DllImport("Kernel32")] 
public static extern int LoadLibrary(String funcname); 

[DllImport("Kernel32")] 
public static extern int FreeLibrary(int handle); 

当我们在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")] 
public static extern int LoadLibrary(String funcname); 

///<summary> 
/// API GetProcAddress 
///</summary> 
[DllImport("Kernel32")] 
public static extern int GetProcAddress(int handle, String funcname); 

///<summary> 
/// API FreeLibrary 
///</summary> 
[DllImport("Kernel32")] 
public static extern int FreeLibrary(int handle); 

///<summary> 
///通过非托管函数名转换为对应的委托, by jingzhongrong 
///</summary> 
///<param name="dllModule">通过LoadLibrary获得的DLL句柄</param> 
///<param name="functionName">非托管函数名</param> 
///<param name="t">对应的委托类型</param> 
///<returns>委托实例,可强制转换为适当的委托类型</returns> 
public static Delegate GetFunctionAddress(int dllModule, string functionName, Type t) 

int address = GetProcAddress(dllModule, functionName); 
if (address == 0) 
return null; 
else 
return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t); 


///<summary> 
///将表示函数地址的IntPtr实例转换成对应的委托, by jingzhongrong 
///</summary> 
public static Delegate GetDelegateFromIntPtr(IntPtr address, Type t) 

if (address == IntPtr.Zero) 
return null; 
else 
return Marshal.GetDelegateForFunctionPointer(address, t); 


///<summary> 
///将表示函数地址的int转换成对应的委托,by jingzhongrong 
///</summary> 
public static Delegate GetDelegateFromIntPtr(int address, Type t) 

if (address == 0) 
return null; 
else 
return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t); 



通过这个类,我们这样调用DLL: 

1、声明相应的委托(正确声明很重要,否则不能调用成功,后面有详细介绍)。 

2、加载DLL: 
int hModule = DLLWrapper.LoadLibrary(dllFilePath); 
if (hModule == 0) 
return false; 

3、获取相应的委托实例: 
FOO foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO)); 
if (foo == null) 

DLLWrapper.FreeLibrary(hModule); 
return false; 


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)] 
struct StructVersionInfo 

public int MajorVersion; 
public int MinorVersion; 

另外,如果单独使用内部类型没有另外使用到字符串、结构、类,可以将结构在C#中声明为class: 
[StructLayout(LayoutKind.Sequential)] 
class StructVersionInfo 

public int MajorVersion; 
public int MinorVersion; 


对应C++中的声明: 
typedef struct _VERSION_INFO 

int MajorVersion; 
int MinorVersion; 
} VERSION_INFO, *PVERSION_INFO; 

如果结构中使用到了字符串,最好应指定相应的字符集: 
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)] 

部分常用的声明对应关系(在结构中): 
C++:字符串数组 
wchar_t Comments[120]; 
C#: 
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 120)] 
public string Comments; 

C++:结构成员 
VERSION_INFO ver; 
C# 
publicStructVersionInfo ver; 

C++:函数指针声明 
PFOO pFoo; //具体声明见文章前面部分 
C#: 
publicIntPtr pFoo; //也可以为 public int pFoo; 
//不同的声明方法可以使用上面DLLWrapper类的相应函数获取对应的委托实例 

如果在结构中使用到了union,那么可以使用FieldOffset指定具体位置。 

3、委托的声明: 

当C++编写的DLL函数需要通过指针传出将一个结构:如以下声明: 
void getVersionInfo(VERSION_INFO *ver); 
对于在C#中声明为class的结构(当VERSION_INFO声明为class) 
delegate voidgetVersionInfo(VERSION_INFO ver); 
如果结构声明为struct,那么应该使用如下声明: 
delegate voidgetVersionInfo(refVERSION_INFO ver); 
注意:应该使用ref关键字。 


如果DLL函数需要传入一个字符串,比如这样: 
BOOL __stdcall jingzhongrong1(const wchar_t* lpFileName, int* FileNum); 
那么使用委托来调用函数的时候应该在C#中如下声明委托: 
delegate bool jingzhongrong1( 
[MarshalAs(UnmanagedType.LPWStr)]String FileName, 
ref int FileNum); 
注意:应该使用[MarshalAs(UnmanagedType.LPWStr)]和String进行声明。 


如果要在DLL函数中传出一个字符串,比如这样: 
void __stdcall jingzhongrong2( 
wchar_t* lpFileName, //要传出的字符串 
int* Length); 
那么我们如下声明委托: 
//使用委托从非托管函数的参数中传出的字符串, 
//应该这样声明,并在调用前为StringBuilder预备足够的空间 
delegate void jingzhongrong2( 
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileName, 
ref int Length, 
); 
在使用函数前,应先为StringBuilder声明足够的空间用于存放字符串: 
StringBuilder fileName = new StringBuilder(FileNameLength);

 

参考: http://www.2cto.com/kf/201007/52562.html

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。