C# .net动态加载第三方DLL
C#动态加载第三方DLL
当我们需要加载第三方非托管DLL时,通常会直接使用DllImport的方式,代码如下:
[DllImport("GetFile.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] static extern string GetFileData(string fileName);
上图的调用方式,默认GetFile.dll文件位于与调用程序(.exe文件)相同的目录中(这里不考虑System32目录、环境变量目录,因为通常情况下,不会将第三方DLL放到这些目录中)。
如果不想将DLL放到exe所在目录,那也可以手动指定DLL文件路径,代码如下:
[DllImport("C:\\Customer\\GetFile.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)] static extern string GetFileData(string fileName);
更进一步,如果此时我们想动态指定DLL文件路径,则以上方式将无法应对,原因是DllImport中的DLL文件路径必须是常量。
为了动态调用DLL,我们需要通过其它方式,具体代码如下。这里我们定义了一个DllInvoke类,其中用到了LoadLibrary()函数,通过该函数导入DLL(如上文中的GetFile.dll)文件,然后再通过GetProcAddress()函数获取DLL中欲使用的API(上文中为GetFileData())的指针,最终通过Marshal.GetDelegateForFunctionPointer()函数返回API对应的委托。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; class DllInvoke { [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll")] private extern static IntPtr GetProcAddress(IntPtr lib, String funcName); [DllImport("kernel32.dll")] private extern static bool FreeLibrary(IntPtr lib); private IntPtr hLib; public DllInvoke(String DllName) { hLib = LoadLibrary(DllName); if (hLib == IntPtr.Zero) { var err = Marshal.GetLastWin32Error(); //只有SetLastError = true时,才能获取到Error Code } } ~DllInvoke() { FreeLibrary(hLib); } //将要执行的函数转换为委托 public Delegate Invoke(String ApiName, Type t) { IntPtr api = GetProcAddress(hLib, ApiName); return (Delegate)Marshal.GetDelegateForFunctionPointer(api, t); } }
另外,有个问题需要注意。当被调用的DLL,它自身也在调用其它DLL时,此时使用上文中的LoadLibrary()函数时,会有值为126的Error Code(通过Marshal.GetLastWin32Error()函数获取Error Code,126表示找不到指定的模块)。之所以会有此报错,是因为对于被调用DLL自身调用的“其它DLL”,会按照和DllImport同样的顺序(即exe所在目录、System32目录、环境变量目录)去寻找它们,而这些“其它DLL”你可能放在了和被调用DLL同样的目录中,而没有放在exe所在目录、System32目录、环境变量目录中,所以显然是找不到的。
如果我就是想把所有的DLL(不论是被调用的DLL,还是被调用DLL自身调用的其它DLL)都放在一个我自己指定的目录中呢?此时我们可以使用LoadLibraryEx()函数,通过该函数导入的DLL,如果它本身也调用了其它DLL的话,会强制先在被调用DLL所在的目录中查找它调用的“其它DLL”。使用LoadLibraryEx()函数的完整代码如下,新的DllInvoke类中,定义了一个LoadLibraryFlags枚举变量,其中的LOAD_WITH_ALTERED_SEARCH_PATH值会被传入LoadLibraryEx()函数中,从而强制在DLL所在目录中查找。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; class DllInvoke { /// <summary> /// LoadLibraryFlags /// </summary> public enum LoadLibraryFlags : uint { DONT_RESOLVE_DLL_REFERENCES = 0x00000001, LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, LOAD_LIBRARY_AS_DATAFILE = 0x00000002, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100, LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 } [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags); [DllImport("kernel32.dll")] private extern static IntPtr GetProcAddress(IntPtr lib, String funcName); [DllImport("kernel32.dll")] private extern static bool FreeLibrary(IntPtr lib); private IntPtr hLib; public DllInvoke(String DllName) { hLib = LoadLibraryEx(DllName, IntPtr.Zero, LoadLibraryFlags.LOAD_WITH_ALTERED_SEARCH_PATH); if (hLib == IntPtr.Zero) { var err = Marshal.GetLastWin32Error(); //只有SetLastError = true时,才能获取到Error Code } } ~DllInvoke() { FreeLibrary(hLib); } //将要执行的函数转换为委托 public Delegate Invoke(String ApiName, Type t) { IntPtr api = GetProcAddress(hLib, ApiName); return (Delegate)Marshal.GetDelegateForFunctionPointer(api, t); } }
如何使用以上定义的DllInvoke类呢?具体代码如下,首先定义一个委托CustomerAPI(该委托的形参列表需要和待调用的DLL中的API一致),然后即可用类似下图Example()函数中的代码,进行API的调用。
public delegate int CustomerAPI(string fileName); private void Example() { string dllName = "C:\\Customer\\GetFile.dll"; DllInvoke customerDll = new DllInvoke(dllName); CustomerAPI GetFileData = (CustomerAPI)customerDll.Invoke("GetFileData", typeof(CustomerAPI)); string fileName = "C:\\Customer\\ExampleFile.txt"; int data = GetFileData(fileName); MessageBox.Show("The data got from file is: " + data.ToString()); }
- 随机文章
- 热门文章
- 热评文章
- C# 非独占延时函数 非Sleep
- C#图像处理(各种旋转、改变大小、柔化、锐化、雾化、底片、浮雕、黑白、滤镜效果)
- C#图片处理示例(裁剪,缩放,清晰度,水印)
- 安装包制作工具 SetupFactory使用1 详解
- C#中的MessageBox消息对话框
- ESP8266在Eclipse下设置编译模式(有boot和无boot)
- 物联网 WIFI 一键配置原理(smartconfig) ESP8266/QCA4004
- C#实现邮件发送的功能