Как правильно загрузить экземпляр из библиотеки DLL, реализующей определенный базовый класс В C#?



У меня есть проблема, когда у меня есть программа, которая должна загрузить плагин (DLL) из определенного каталога, где DLL реализует определенный базовый класс. Проблема в том, что моя программа, которая загружает DLL, имеет ссылку на другую DLL, на которую также ссылается загружаемая DLL. Я покажу на примере, как возникает эта проблема. Этот простой тест состоит из 3-х различных решений и 3-х отдельных проектов. Примечание: Если у меня есть все проекты в одном решении, проблема не возникает. возникать.



Решение 1-проект, определяющий базовый класс и интерфейс
Адаптерная база.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-основная программа, которая загружает библиотеку DLL, реализующую AdapterBase*
**Программа.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;
}
}
}


Так что теперь основной программы иногда.cs попытается загрузить DLL из path C:testAdapter и это работает прекрасно, если я только помещу файл MyAdapter.dll в этой папке. Однако Решение 2 (MyAdapter.КС), положим и MyAdapter.dll и AdapterBase.dll в выходной папке bin/. Теперь, если скопировать оба этих файла в c:testAdapter экземпляр из библиотеки DLL не загружается с момента сравнения
Если (тип.IsSubclassOf (typeof (AdapterBase))) { терпит неудачу в MyProgram.cs .



С Иногда.cs уже имеет ссылку на AdapterBase.dll там, кажется, есть какой-то конфликт в другой DLL загружается из другого пути, который ссылается на ту же DLL. Загруженная библиотека DLL, по-видимому, сначала разрешает свои зависимости от библиотек DLL в той же папке. Я думаю, что это связано с контекстами сборки и некоторой проблемой с методом LoadFrom, но я не знаю, как заставить C# понять, что это на самом деле та же DLL, которую он уже загрузил.



Решение, конечно, только скопировать единственную необходимую DLL но моя программа была бы гораздо более надежной, если бы она могла справиться с тем, что другая общая DLL также была там. Я имею в виду, что они на самом деле одинаковы. Итак, какие-либо решения, как сделать это более надежным?

540   2  

2 ответов:

Да, это проблема идентификации типов. Идентификатор типа .NET-это не только пространство имен и имя типа, но и сборка, из которой он был создан. Ваш плагин имеет зависимость от сборки, которая содержит IAdapter, когда LoadFrom () загружает плагин, ему также понадобится эта сборка. Среда CLR находит его в контексте LoadFrom, другими словами в c:\test\adapter каталог, как правило, очень желательно, так как это позволяет плагинам использовать свои собственные версии DLL.

Просто не в этот случай. Это пошло не так, потому что вы подключаемое решение послушно скопировали зависимости. Обычно очень желательно, но не в этом случае.

Вы будете иметь, чтобы остановить его от копирования IAdapter собрания:

  • откройте решение плагина и используйте Build > Clean.
  • удалите оставшуюся копию сборки IAdapter из выходного каталога с помощью проводника.
  • выберите сборку IAdapter в узле ссылки решения плагина. Задайте свойству Copy Local значение Ложный.
  • Используйте Build > Build и убедитесь, что сборка IAdapter действительно больше не копируется.

Copy Local суть в том, что остальные пули находятся там только для того, чтобы убедиться, что старая копия не вызывает проблем. Поскольку CLR больше не может найти сборку IAdapter "простым способом", он вынужден продолжать ее искать. Теперь находим его в контексте загрузки, другими словами, в каталоге, где установлен исполняемый файл хоста. Уже нагруженный, никакая потребность нагрузить снова один-и-единственный. Проблема решена.

Я нашел решение своей проблемы, хотя путь к DLL не может быть полностью произвольным. Я смог поместить библиотеки DLL в, например, bin/MyCustomFolder и загрузить библиотеку DLL, не получив проблему конфликта типов.

Решение состояло в использовании метода Assembly.Load(), который принимает полное имя сборки в качестве аргумента. Поэтому сначала я нахожу имя сборки, загружая все библиотеки DLL в указанную папку и используя Assembly.GetTypes() и проверяя, является ли тип подклассом AdapterBase. потом я используйте Assembly.Load() для фактической загрузки сборки, которая элегантно загружает DLL без каких-либо конфликтов типов.

Программа.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 в совершенно произвольном месте. Обновите свое приложение .config as ниже:

Приложение.config

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="MyCustomFolder"/>
    </assemblyBinding>
  </runtime>
</configuration>

Совет: На самом деле у меня была эта проблема для веб-приложения, которое я создал. В этом случае вы должны обновить Web.вместо этого config . Кроме того, в этом случае путь зондирования не содержит исполняемую сборку в качестве корня, но фактически является корнем вашего веб-приложения. Таким образом, в этом случае вы можете поместить вашу папку DLL MyCustomFolder непосредственно в корневую папку веб-приложений, например: inetpub\wwwroot\mywebapp\MyCustomFolder и затем обновить Web.config как приложение.конфигурация выше.

Comments

    Ничего не найдено.