2013-06-03 51 views
5

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

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

硬件设备的新版本现在与不同的硬件DLL一起发布。旧的DLL与新硬件不兼容,新的DLL与旧硬件不兼容。

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

+2

您首先需要能够确定您的目标硬件。你解决了吗? –

+0

我可以通过下拉选择来实现 - 或者,理想情况下,通过加载一个DLL并扫描硬件,然后加载另一个DLL并扫描硬件..如果可能的话.. – Mark

+0

中间DLL是本地DLL吗? – Dennis

回答

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

这样做的好处在于,您在代码中使用了抽象类型,然后在运行时,适配器编译器将编译一个新类,该类将使用其他类型来完成该抽象类型,如目标类型。它也很高效。

+0

谢谢,这给了我最终解决方案的最多线索。 – Mark

0

编辑:

如果中间DLL是.NET程序集,您可以使用方法中提到here指定到哪里寻找你的中间DLL 之前,你调用使用中间DLL的任何方法,而不必更改现有的代码。

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

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

以下讨论仍适用于硬件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-调用方法(dllimport的)将导致DLL的相应版本被加载。要立即加载所有的DLL,你可以参考DllImport, how to check if the DLL is loaded?。但是,如果您希望在不重新启动应用程序的情况下一起使用这两个DLL版本,或者在方法名称和/或参数计数/类型级别上的两个DLL之间存在任何API差异,则必须创建两个独立的P-Invoke方法集,每个方法都绑定到相应版本的中间DLL。

+0

这似乎不适用于我的项目。如果我按照链接中的建议调用'Marshal.PrelinkAll(Type)',它也没有帮助。知道它是否真的做了什么也没有回报价值。 – Mark

+0

重新更新,这听起来像我已经做了,但没有运气:( – Mark

0

我想让这两个DLL在程序中共存,您必须使用AppDomains,如here所解释。

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

+0

我可以看到这可以用来加载中间DLL,但如果这调用硬件DLL,有没有任何指定它应该的方式使用给定的AppDomain? – Mark

+0

我不确定如果这两个中间件DLL在不同的文件夹中,并且相应的硬件位于同一个文件夹中,我想(希望?)中间件将简单地从它们各自的文件夹。 – C4stor

+1

是中间DLL一个.Net程序集? – Interarticle