如何从在C#中实现特定基类的DLL中正确加载实例?

时间:2015-10-22 12:18:27

标签: c# dll

我有一个问题,我有一个程序应该从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也存在,我的程序会更强大。我的意思是他们实际上是一样的。那么任何解决方案如何做到这一点更加健壮?

2 个答案:

答案 0 :(得分:2)

是的,这是一种类型身份问题。 .NET类型的标识不仅仅是命名空间和类型名称,它还包括它来自的程序集。您的插件依赖于包含IAdapter的程序集,当LoadFrom()加载插件时,它也需要该程序集。 CLR在LoadFrom上下文中找到它,换句话说在c:\ test \ adapter目录中,通常非常需要,因为它允许插件使用自己的DLL版本。

不是在这种情况下。这是错误的,因为您的插件解决方案尽职尽责地复制了依赖项。通常非常需要,但不是在这种情况下。

你必须阻止它复制IAdapter程序集:

  • 打开插件解决方案并使用Build&gt;干净。
  • 使用资源管理器删除输出目录中的IAdapter程序集的剩余副本。
  • 在插件解决方案的References节点中选择IAdapter程序集。将其Copy Local属性设置为False。
  • 使用Build&gt;构建并验证IAdapter程序集确实不再被复制。

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。