Страницы

Поиск по вопросам

понедельник, 6 мая 2019 г.

Как правильно использовать методы сборки в другом домене?

Если создать домен и загрузить в него сборку, а потом используя метод GetAssemblies, достать сборки, то выгружая домен, сборка остается в основном домене приложения и с ней ничего сделать нельзя, удалить например.
Каким образом можно использовать методы сборки из чужого домена, при этом выгружая домен выгрузить и сборку из приложения?
Вот код которым я это делаю:
using System; using System.IO; using System.Reflection;
namespace Test { internal static class Program { private static void Main() { var appDomain = AppDomain.CreateDomain( "test" ); AssemblyLoader.Run(appDomain);
Console.WriteLine("Сборка загружена");
foreach ( var assembly in appDomain.GetAssemblies () ) { Console.WriteLine ( assembly.GetName () ); }
Console.ReadLine();
AppDomain.Unload( appDomain );
Console.WriteLine("Домен выгружен"); //Не удается удалить сборку из каталога, она не выгрузилась! Console.ReadLine(); } }
public static class AssemblyLoader { public static void Run( AppDomain appDomain ) { appDomain.DoCallBack( Invoke ); }
private static void Invoke() { Assembly.LoadFile( Path.Combine( Environment.CurrentDirectory, "test.dll" ) ); }
}
}


Ответ

Если вы передаёте объект из другого домена, вы этим самым загружаете его в ваш домен, чего вы как раз хотите избежать. Решением может быть передавать примитивные типы. Чтобы не ограничивать себя простыми колбеками, вам нужно воспользоваться MarshalByRefObject (они не копируются между доменами, а выполняются в том домене, где созданы).
Итак, вот рабочий код.
Это плагин, DomainsPlugin.dll:
namespace DomainsPlugin { public class Plugin { public IEnumerable GetNames() { return AppDomain.CurrentDomain.GetAssemblies().Select(asm => asm.GetName()); } } }
Ничего особого, просто рабочий метод.
Теперь главная программа:
namespace DomainsMain { class Program { static void Main(string[] args) { // в этом каталоге бежит программа var executableDir = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location);
// это каталог, где лежит плагин. // у вас он будет лежать, понятно, в другом месте var pluginDir = Path.GetFullPath(Path.Combine( executableDir, "..", "..", "..", "DomainsPlugin", "bin", "Debug"));
// оригинал плагина var pluginSourceFile = Path.Combine(pluginDir, "DomainsPlugin.dll"); // а сюда мы его скопируем, и отсюда будем загружать var pluginWorkingFile = Path.Combine(executableDir, "DomainsPlugin.dll");
// копируем File.Copy(pluginSourceFile, pluginWorkingFile, overwrite: true);
// окей, теперь новый домен var pluginDomain = AppDomain.CreateDomain("Plugin");
// теперь, создаём экземпляр нашего класса в новом домене // у нас на него реально лишь прокси-объект var resident = (Resident)pluginDomain.CreateInstanceAndUnwrap( Assembly.GetExecutingAssembly().FullName, "DomainsMain.Resident"); // получили данные resident.Obtain();
// выводим их foreach (var r in resident.Result) Console.WriteLine(r.ToString());
// выгружаем домен... AppDomain.Unload(pluginDomain);
// ... и удаляем плагин File.Delete(pluginWorkingFile); } }
// простой класс, будет выполняться в другом домене public class Resident : MarshalByRefObject { public List Result;
public void Obtain() { // загружаем библиотеку var asm = Assembly.LoadFrom("DomainsPlugin.dll"); var type = asm.GetType("DomainsPlugin.Plugin"); // и вызываем функцию из неё через рефлексию // можно было бы закастить к интерфейсу, если объявить его в // этой или другой общей dll-ке var p = Activator.CreateInstance(type); var method = type.GetMethod( "GetNames", BindingFlags.Instance | BindingFlags.Public); var result = (IEnumerable)method.Invoke(p, null); // сохраняем результат. это поле будет доступно из первоначального домена Result = result.ToList(); } } }
На моей машине из-под Visual Studio выдаёт:
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Microsoft.VisualStudio.HostingProcess.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a DomainsMain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 DomainsPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Обновление: резидентная функция (Obtain()) вполне может возвращать значение. Но это значение должно быть сериализируемым, чтобы «протиснуться» между доменами.
Для нашего примера, подправляем класс Resident
public class Resident : MarshalByRefObject { public List Obtain() // <-- поменяли возвращаемый тип { // и убрали свойство Result // загружаем библиотеку var asm = Assembly.LoadFrom("DomainsPlugin.dll"); var type = asm.GetType("DomainsPlugin.Plugin"); // и вызываем функцию из неё через рефлексию // можно было бы закастить к интерфейсу, если объявить его в // этой или другой общей dll-ке var p = Activator.CreateInstance(type); var method = type.GetMethod( "GetNames", BindingFlags.Instance | BindingFlags.Public); var result = (IEnumerable)method.Invoke(p, null); return result.ToList(); } }
Теперь вызов из основного кода выглядит так:
// получили данные var result = resident.Obtain();
// выводим их foreach (var r in result) Console.WriteLine(r.ToString());

Комментариев нет:

Отправить комментарий