当前位置:首页 > C# > 正文内容

C# .net动态加载第三方DLL

admin2年前 (2023-04-03)C#3925

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());
}


扫描二维码推送至手机访问。

版权声明:本文由视觉博客发布,如需转载请注明出处。

本文链接:https://feelsight.cn/post/139.html

“C# .net动态加载第三方DLL” 的相关文章

高恪路由器批量管理监控系统

高恪路由器批量管理监控系统

一、采用C# 编写,可以批量监控路由器是否正常 功能: 1.显示当前路由IP、名字、实时上行速度、实时下行速度、主机数量、连接数、CPU占用率、内存占用率、CPU温度、运行时间等 2.可以显示10分钟实时流量 3.可以显示历史2小时、...

c# 委托实现多线程的实例/另一个class类调用控件

c# 委托实现多线程的实例/另一个class类调用控件

一、使用线程在窗体中显示进度条 在Winform应用程序中,经常用进度条显示进度信息。这时候就可能用到多线程。如果不采用多线程控制进度条的话,窗口界面很容易假死(无法看到进度信息,看起来像界面卡住了)。 在这个实例中,我们建立一个窗体,窗体中包括一个后台组件,一个进度条控件。当在主窗...

C#调用c++的dll执行带参数的函数时 请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配

C#动态调用DLL 由于Dll路径的限制,使用的不是很方便,C#中我们经常通过配置动态的调用托管Dll,例如常用的一些设计模式:Abstract Factory, Provider, Strategy模式等等,那么是不是也可以这样动态调用C++动态链接呢?只要您还记得在C++中,通...

使用PInvoke互操作,让C#和C++愉快的交互优势互补

使用PInvoke互操作,让C#和C++愉快的交互优势互补

一:背景 1. 讲故事 如果你常翻看FCL的源码,你会发现这里面有不少方法借助了C/C++的力量让C#更快更强悍,如下所示: [DllImport("QCall", CharSet = CharSet.Unicode)] [Securit...

C# 与 C++ 数据类型对照

C++            C# ===================================== WORD        &n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。