我有一个问题,我有一个程序应该从DLL实现特定基类的特定目录加载插件(DLL)。问题是我加载DLL的程序引用了另一个DLL,正在加载的DLL也引用了该DLL。我将展示问题是如何产生的一个例子。这个简单的测试包括3个不同的解决方案和3个独立的项目注意:如果我在同一解决方案中拥有所有项目,则不会出现问题。
解决方案1 - 定义Base类和接口的项目 的 AdapterBase.cs
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
解决方案2 - 定义基类实现的项目 的 MyAdapter.cs
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
解决方案3 - 加载实现AdapterBase *的DLL的主程序 **的Program.cs
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
所以现在主程序 MyProgram.cs 将尝试从路径 C:\ test \ Adapter 加载DLL,如果我 ONLY < / strong>将文件 MyAdapter.dll 放入该文件夹中。但是,解决方案2( MyAdapter.cs )会将 MyAdapter.dll 和 AdapterBase.dll 放在输出bin /目录中。现在,如果将这些文件复制到 c:\ test \ Adapter ,则自从比较后,DLL中的实例不会被加载 if(type.IsSubclassOf(typeof(AdapterBase))){在 MyProgram.cs 中失败。
由于 MyProgram.cs 已经有对AdapterBase.dll的引用,因此在从引用相同DLL的其他路径加载的其他DLL中似乎存在一些冲突。加载的DLL似乎首先解析其对同一文件夹中的DLL的依赖性。我认为这与汇编上下文和LoadFrom方法的一些问题有关,但我不知道如何让C#意识到它实际上是它已经加载的相同的DLL。
解决方案当然只是复制唯一需要的DLL,但如果它可以处理其他共享DLL也存在,我的程序会更强大。我的意思是他们实际上是一样的。那么任何解决方案如何做到这一点更加健壮?
答案 0 :(得分:2)
是的,这是一种类型身份问题。 .NET类型的标识不仅仅是命名空间和类型名称,它还包括它来自的程序集。您的插件依赖于包含IAdapter的程序集,当LoadFrom()加载插件时,它也需要该程序集。 CLR在LoadFrom上下文中找到它,换句话说在c:\ test \ adapter目录中,通常非常需要,因为它允许插件使用自己的DLL版本。
不是在这种情况下。这是错误的,因为您的插件解决方案尽职尽责地复制了依赖项。通常非常需要,但不是在这种情况下。
你必须阻止它复制IAdapter程序集:
Copy Local
属性设置为False。 Copy Local
是本质,其余的子弹只是为了确保旧版本不会导致问题。由于CLR无法再以“简单的方式”找到IAdapter组件,因此不得不继续寻找它。现在在Load上下文中找到它,换句话说,就是安装主机可执行文件的目录。已加载,无需再次加载“一对一”。问题解决了。
答案 1 :(得分:0)
我找到了解决问题的方法,虽然DLL的路径不能完全随意。我能够将DLL放入例如 bin / MyCustomFolder 并加载DLL而不会遇到类型冲突问题。
解决方案是使用Assembly.Load()
方法,该方法将完整的程序集名称作为参数。首先,我通过加载指定文件夹中的所有DLL并使用Assembly.GetTypes()
并检查Type是否是AdapterBase
的子类来找到程序集的名称。然后我使用Assembly.Load()
来实际加载程序集,它可以优雅地加载DLL而不会出现任何类型冲突。
<强> Program.cs的强>
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string dir = Path.GetDirectoryName(path);
string pathToLoad = Path.Combine(dir, "MyCustomFolder");
AdapterBase adapter = LoadAdapterFromPath(pathToLoad);
adapter.PrintHello();
}
/// <summary>
/// Loads the adapter from path. LoadFile will be used to find the correct type and then Assembly.Load will be used to actually load
/// and instantiate the class.
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static AdapterBase LoadAdapterFromPath(string dir) {
string assemblyName = FindAssembyNameForAdapterImplementation(dir);
Assembly assembly = Assembly.Load(assemblyName);
Type[] types = assembly.GetTypes();
Type adapterType = null;
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
adapterType = type;
break;
}
}
AdapterBase adapter;
try {
adapter = (AdapterBase)Activator.CreateInstance(adapterType);
} catch (Exception e) {
adapter = null;
}
return adapter;
}
public static string FindAssembyNameForAdapterImplementation(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
foreach (var file in files)
{
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeof(AdapterBase)))
{
return assembly.FullName;
}
}
}
return null;
}
}
}
注意:为Assembly.Load()
添加额外的探测路径以在 bin / MyCustomFolder 中查找程序集也很重要。探测路径必须是执行程序集的子目录,因此不可能将DLL置于完全任意的位置。更新您的 App.config ,如下所示:
<强>的App.config 强>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="MyCustomFolder"/>
</assemblyBinding>
</runtime>
</configuration>
提示:实际上我为我创建的Web应用程序遇到了这个问题。在这种情况下,您应该更新 Web.config 。同样在这种情况下,探测路径没有以root身份执行程序集,但实际上是Web应用程序的根目录。因此,在这种情况下,您可以直接在您的Web应用程序根文件夹中放置DLL文件夹 MyCustomFolder ,例如: inetpub \ wwwroot \ mywebapp \ MyCustomFolder ,然后将Web.config更新为上面的App.config。