6

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

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

新版本的硬件设备现已发布,带有不同的硬件 D​​LL。旧 DLL 与新硬件不兼容,新 DLL 与旧硬件不兼容。

我怎样才能使我的应用程序与这两种硬件一起工作?我想我需要根据需要加载和卸载每个 DLL?

4

3 回答 3

4

这是我为类似问题所做的。我有一大段代码要使用,但我必须在运行时加载 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 匹配的新类,并使其映射到您想要的实际对象的实例上。 几年前我写了一篇关于如何做到这一点的博客

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

于 2013-06-03T18:21:27.477 回答
0

编辑:

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

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

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

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


以下是有效的,如果:

  1. 您一次只需要一个版本的 dll(在您的应用程序运行的整个期间),并且
  2. 中间 DLL 的两个版本具有完全相同的 API。

根据 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,如何检查 DLL 是否已加载?.

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

于 2013-06-03T08:06:07.313 回答
0

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

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

于 2013-06-03T08:08:07.433 回答