加载多个版本的DLL

时间:2013-06-03 07:45:49

标签: c# dll

我有一个C#应用程序,它与某些硬件(USB设备)连接如下: C# application -> intermediate DLL -> hardware DLL -> hardware。中间DLL和硬件DLL随USB设备提供,因此我无法控制这些。

中间DLL 是我需要在VS项目中包含的唯一内容,因为这就是我所说的。然后硬件DLL位于同一目录中,因此必须自动找到。

现在发布了一个新版本的硬件设备,其中包含不同的硬件DLL 。旧DLL与新硬件不兼容,新DLL与旧硬件不兼容。

如何使我的应用程序适用于这两个硬件?我想我需要根据需要加载和卸载每个DLL?

3 个答案:

答案 0 :(得分:3)

这是我为类似问题所做的事情。我有一大堆我想要使用的代码,但我必须在运行时加载dll。所以我在我的项目中引用它,但是我没有将它放在与其余程序集相同的目录中。相反,在使用代码中,我有一些看起来像这样的代码:

// constructor called from a static constructor elsewhere
MyDllLoader(string hardwareFolder) {
    _hardwareFolder = hardwareFolder;
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    SeeIfAlreadyLoaded();
}


private void SeeIfAlreadyLoaded() {
    // if the assembly is still in the current app domain then the AssemblyResolve event will
    // never fire.
    // Since we need to know where the assembly is, we have to look for it
    // here.
    Assembly[] assems = AppDomain.CurrentDomain.GetAssemblies();
    foreach (Assembly am in assems)
    {
        // if it matches, just mark the local _loaded as true and get as much
        // other information as you need
    }
}

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
    string name = args.Name;
    if (name.StartsWith("Intermediate.dll,"))
    {
        string candidatePath = Path.Combine(_hardwareFolder, "Intermediate.dll");
        try {
            Assembly assem = Assembly.LoadFrom(candidatePath);
            if (assem != null) {
                _location = candidateFolder;
                _fullPath = candidatePath;
                _loaded = true;
                return assem;
            }
        }
        catch (Exception err) {
            sb.Append(err.Message);
        }
    }
    return null;
}

还有另一个解决方案 - 它很复杂,但我已经完成了它并为你完成了工作。您声明了一个抽象类,比如说MyHardwareAbstraction,它具有您想要的方法的签名,并且您可以针对该接口进行编码。然后编写一些代码,这些代码给出了程序集的路径,加载它并动态定义一个匹配MyHardwareAbstraction的新类,并使其映射到您想要的实际对象的实例上。 I wrote a blog several years ago on how to do this

执行此操作的好处是您在代码中使用抽象类型并对其进行操作,然后适配器编译器将在运行时编译一个新类,该类将使用其他类型作为抽象类型来完成目标类型。它也很有效率。

答案 1 :(得分:0)

修改

如果中间DLL是.Net程序集,您可以使用前面提到的here方法来指定在调用使用中间DLL的任何方法之前查找中间DLL 的位置,而无需更改现有代码。

然后你不能直接引用你的C#项目中的DLL,因为在你的Main方法被调用之前会发现并加载.Net程序集。相反,您必须使用AppDomain或其他方法动态加载中间DLL,然后通过反射或使用dynamic对象来使用库。

显然,这会使编程变得非常繁琐。但是,还有另一种方法。您可以编写启动程序,它可以加载原始应用程序(您可以将.exe文件作为库加载),并反射性地调用原始程序的Main方法。为确保加载了正确的中间DLL,您可以使用提及here的方法,而启动程序正在加载原始应用程序。

以下讨论仍适用于硬件DLL。


如果符合以下条件,则以下内容有效:

  1. 您一次只需要一个版本的dll(在应用程序运行的整个期间),
  2. 两个版本的中间DLL具有完全相同的API。
  3. 根据MSDN,DLL搜索路径包括在PATH环境变量下指定的目录。 (http://msdn.microsoft.com/en-us/library/7d83bc18%28v=vs.80%29.aspx)。因此,您可以将两个版本的中间DLL放在应用程序目录下的单独子目录下,但每个目录下的名称完全相同,例如:

    bin\
       hardware-intermediate-v1\
           intermediate.dll
       hardware-intermediate-v2\
           intermediate.dll
    

    然后,在启动时,在您的应用程序确定要使用的版本之后,您可以将以上目录之一添加到PATH环境变量中,

    using System;
    using System.Reflection;
    using System.IO;
    ...
    Environment.SetEnvironmentVariable(
        "PATH", 
        Environment.GetEnvironmentVariable("PATH") + ";" + 
            Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + 
            "\\hardware-intermediate-v1"
    );
    

    然后,调用P-Invoke方法(DLLImport)将导致加载相应版本的DLL。要立即加载所有DLL,您可以参考DllImport, how to check if the DLL is loaded?

    但是,如果您希望在不重新启动应用程序的情况下将两个版本的DLL一起使用,或者在方法名称和/或参数计数/类型级别上两个DLL之间存在任何API差异,则必须创建两个独立的P-Invoke方法集,每个方法绑定到其对应的中间DLL版本。

答案 2 :(得分:0)

我希望这两个dll在程序中共存,你必须使用AppDomains,如here所述。

否则,您可以在用户明确选择他需要的版本后使用LoadLibrary吗?