我有一个C#应用程序,它与某些硬件(USB设备)连接如下:
C# application -> intermediate DLL -> hardware DLL -> hardware
。中间DLL和硬件DLL随USB设备提供,因此我无法控制这些。
中间DLL 是我需要在VS项目中包含的唯一内容,因为这就是我所说的。然后硬件DLL位于同一目录中,因此必须自动找到。
现在发布了一个新版本的硬件设备,其中包含不同的硬件DLL 。旧DLL与新硬件不兼容,新DLL与旧硬件不兼容。
如何使我的应用程序适用于这两个硬件?我想我需要根据需要加载和卸载每个DLL?
答案 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。
如果符合以下条件,则以下内容有效:
根据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吗?