您的位置:首页 > 编程语言 > C语言/C++

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

2014-03-14 00:47 295 查看
作者:天涯 来源:中国自学编程网 发布日期:1210203711
动态调用
by jingzhongrong 2008-05-08

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

[align=left][DllImport("Kernel32")][/align]
[align=left]public static extern int GetProcAddress(int handle, String funcname);[/align]
[align=left] [/align]
[align=left][DllImport("Kernel32")][/align]
[align=left]public static extern int LoadLibrary(String funcname);[/align]
[align=left] [/align]
[align=left][DllImport("Kernel32")][/align]
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中的函数了:

[align=left]public class DLLWrapper[/align]
[align=left]{[/align]
[align=left] ///<summary>[/align]
[align=left] /// API LoadLibrary[/align]
[align=left] ///</summary>[/align]
[align=left] [DllImport("Kernel32")][/align]
[align=left] public static extern int LoadLibrary(String funcname);[/align]
[align=left] [/align]
[align=left] ///<summary>[/align]
[align=left] /// API GetProcAddress[/align]
[align=left] ///</summary>[/align]
[align=left] [DllImport("Kernel32")][/align]
[align=left] public static extern int GetProcAddress(int handle, String funcname);[/align]
[align=left] [/align]
[align=left] ///<summary>[/align]
[align=left] /// API FreeLibrary[/align]
[align=left] ///</summary>[/align]
[align=left] [DllImport("Kernel32")][/align]
[align=left] public static extern int FreeLibrary(int handle);[/align]
[align=left] [/align]
[align=left] ///<summary>[/align]
[align=left] ///通过非托管函数名转换为对应的委托, by jingzhongrong[/align]
[align=left] ///</summary>[/align]
[align=left] ///<param name="dllModule">通过LoadLibrary获得的DLL句柄</param>[/align]
[align=left] ///<param name="functionName">非托管函数名</param>[/align]
[align=left] ///<param name="t">对应的委托类型</param>[/align]
[align=left] ///<returns>委托实例,可强制转换为适当的委托类型</returns>[/align]
[align=left] public static Delegate GetFunctionAddress(int dllModule, string functionName, Type t)[/align]
[align=left] {[/align]
[align=left] int address = GetProcAddress(dllModule, functionName);[/align]
[align=left] if (address == 0)[/align]
[align=left] return null;[/align]
[align=left] else[/align]
[align=left] return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] ///<summary>[/align]
///将表示函数地址的IntPtr实例转换成对应的委托,
by jingzhongrong
[align=left] ///</summary>[/align]
[align=left] public static Delegate GetDelegateFromIntPtr(IntPtr address, Type t)[/align]
[align=left] {[/align]
[align=left] if (address == IntPtr.Zero)[/align]
[align=left] return null;[/align]
[align=left] else[/align]
[align=left] return Marshal.GetDelegateForFunctionPointer(address, t);[/align]
[align=left] }[/align]
[align=left] [/align]
[align=left] ///<summary>[/align]
///将表示函数地址的int转换成对应的委托,by
jingzhongrong
[align=left] ///</summary>[/align]
[align=left] public static Delegate GetDelegateFromIntPtr(int address, Type t)[/align]
[align=left] {[/align]
[align=left] if (address == 0)[/align]
[align=left] return null;[/align]
[align=left] else[/align]
[align=left] return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);[/align]
[align=left] }[/align]
}

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

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

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

3、获取相应的委托实例:
[align=left]FOO foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO));[/align]
[align=left]if (foo == null)[/align]
[align=left]{[/align]
[align=left] DLLWrapper.FreeLibrary(hModule);[/align]
[align=left] return false;[/align]
}

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:
[align=left] [StructLayout(LayoutKind.Sequential)][/align]
[align=left] struct StructVersionInfo[/align]
[align=left] {[/align]
[align=left] public int MajorVersion;[/align]
[align=left] public int MinorVersion;[/align]
}
另外,如果单独使用内部类型没有另外使用到字符串、结构、类,可以将结构在C#中声明为class:
[align=left] [StructLayout(LayoutKind.Sequential)][/align]
[align=left] class StructVersionInfo[/align]
[align=left] {[/align]
[align=left] public int MajorVersion;[/align]
[align=left] public int MinorVersion;[/align]
}

对应C++中的声明:
[align=left] typedef struct _VERSION_INFO[/align]
[align=left] {[/align]
[align=left] int MajorVersion;[/align]
[align=left] int MinorVersion;[/align]
} VERSION_INFO, *PVERSION_INFO;

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

部分常用的声明对应关系(在结构中):
C++:字符串数组
wchar_t Comments[120];
[align=left]C#:[/align]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst =
120)]
[align=left] public string Comments;[/align]
[align=left] [/align]
[align=left]C++:结构成员[/align]
[align=left] VERSION_INFO ver;[/align]
[align=left]C#[/align]
[align=left] publicStructVersionInfo ver;[/align]
[align=left] [/align]
[align=left]C++:函数指针声明[/align]
[align=left] PFOO pFoo; //具体声明见文章前面部分[/align]
[align=left]C#:[/align]
publicIntPtr pFoo; //也可以为 public
int pFoo;
[align=left] //不同的声明方法可以使用上面DLLWrapper类的相应函数获取对应的委托实例[/align]
[align=left] [/align]
[align=left]如果在结构中使用到了union,那么可以使用FieldOffset指定具体位置。[/align]
[align=left] [/align]
[align=left]3、委托的声明:[/align]
[align=left] [/align]
[align=left]当C++编写的DLL函数需要通过指针传出将一个结构:如以下声明:[/align]
[align=left] void getVersionInfo(VERSION_INFO *ver);[/align]
[align=left]对于在C#中声明为class的结构(当VERSION_INFO声明为class)[/align]
[align=left] delegate voidgetVersionInfo(VERSION_INFO ver);[/align]
[align=left]如果结构声明为struct,那么应该使用如下声明:[/align]
[align=left] delegate voidgetVersionInfo(refVERSION_INFO ver);[/align]
[align=left]注意:应该使用ref关键字。[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]如果DLL函数需要传入一个字符串,比如这样:[/align]
[align=left] BOOL __stdcall jingzhongrong1(const wchar_t* lpFileName, int* FileNum);[/align]
[align=left]那么使用委托来调用函数的时候应该在C#中如下声明委托:[/align]
[align=left] delegate bool jingzhongrong1([/align]
[align=left] [MarshalAs(UnmanagedType.LPWStr)]String FileName,[/align]
[align=left] ref int FileNum);[/align]
[align=left]注意:应该使用[MarshalAs(UnmanagedType.LPWStr)]和String进行声明。[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]如果要在DLL函数中传出一个字符串,比如这样:[/align]
[align=left] void __stdcall jingzhongrong2([/align]
[align=left] wchar_t* lpFileName, //要传出的字符串[/align]
[align=left] int* Length);[/align]
[align=left]那么我们如下声明委托:[/align]
[align=left] //使用委托从非托管函数的参数中传出的字符串,[/align]
[align=left] //应该这样声明,并在调用前为StringBuilder预备足够的空间[/align]
[align=left] delegate void jingzhongrong2([/align]
[align=left] [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileName,[/align]
[align=left] ref int Length,[/align]
[align=left] );[/align]
[align=left]在使用函数前,应先为StringBuilder声明足够的空间用于存放字符串:[/align]
[align=left] StringBuilder fileName = new StringBuilder(FileNameLength);[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: