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

    技术2022-05-11  24

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

            by jingzhongrong  2008-05-08 动态加载 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#     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(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, // 要传出的字符串     intLength); 那么我们如下声明委托:     // 使用委托从非托管函数的参数中传出的字符串,     // 应该这样声明,并在调用前为 StringBuilder 预备足够的空间     delegate void jingzhongrong2(        [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileName,        ref int Length,     ); 在使用函数前,应先为StringBuilder声明足够的空间用于存放字符串:     StringBuilder fileName = new StringBuilder(FileNameLength);  

    最新回复(0)