Как стать автором
Обновить
0

Делаем отгружаемые сборки: взаимодействуем между доменами без маршаллинга

Время на прочтение3 мин
Количество просмотров12K


Ссылка на проект в GitHub: DotNetEx

На множественных ресурсах время от времени задается вопрос. Можно ли сделать отгружаемые сборки с текущего домена? Так, чтобы попользовался и «давай, до свидания!»? Везде и всегда ответ, который давался – это «нет». Ведь единственное, что можно выгрузить – это домен. Соответственно, если хочется наладить отгрузку, сборку надо помещать в домен, и налаживать между доменами взаимодействие через сериализуемые типы. А это — очень медленное взаимодействие. А мы скажем так. Можно. С ньюансами. Загружать мы будем также в отдельный домен. Но отменим сериализацию при вызове методов между доменами.

Вопросы, которые мы будем решать:
  • Создание домена с возможностью отдачи объекта из домена в родительский
  • Выгрузка сборки


Решение проблемы


Итак, как всегда, будем решать проблемы по мере появления:
  • Как мы уже выяснили в прошлых статьях, память общая и не зависит от доменов. А это значит, что если найти способ передачи указателя на объект, то можно научиться без сериализации передавать объекты между доменами.

    Возьмем некий общий тип. Для упрощения, возьмем тип из mscorlib: IServiceProvider.
    Создадим сборку, которую мы собираемся подружить с возможностью отгрузки:
    public class Implementation : IServiceProvider
        {
            public override string ToString()
            {
                return "Awesome";
            }
    
            public object GetService(Type serviceType)
            {
                return new object();
            }
        }
    

  • Теперь напишем класс, который будет создавать домен и уметь создавать в этом домене экземпляры классов:
        public class AppDomainRunner : MarshalByRefObject, IDisposable
        {
            private AppDomain appDomain;
            private Assembly assembly;
            private AppDomainRunner remoteRunner;
    
            private void LoadAssembly(string assemblyPath)
            {
                assembly = Assembly.LoadFile(assemblyPath);
            }
    
            public AppDomainRunner(string assemblyPath)
            {
                // make appdomain
                appDomain = AppDomain.CreateDomain("PseudoIsolated", null, 
                    new AppDomainSetup
                    {
                        ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
                    });
    
                // create object instance
                remoteRunner = (AppDomainRunner)appDomain.CreateInstanceAndUnwrap(typeof(AppDomainRunner).Assembly.FullName, typeof(AppDomainRunner).FullName);
                remoteRunner.LoadAssembly(assemblyPath);
            }
    
            public IntPtr CreateInstance(string typename)
            {
                return remoteRunner.CreateInstanceImpl(typename);
            }
    
            private IntPtr CreateInstanceImpl(string typename)
            {
                return EntityPtr.ToPointer(assembly.CreateInstance(typename));
            }
    
            public void Dispose()
            {
                assembly = null;
                remoteRunner = null;
                AppDomain.Unload(appDomain);
            }
    
    
  • Теперь напишем класс для IoC rконтейнера:
        public class Container : IDisposable
        {
            private AppDomainRunner appdomain;
    
            private Dictionary<Type, Object> instances = new Dictionary<Type, object>(); 
    
            public Container(string assemblyName)
            {
                appdomain = new AppDomainRunner(Path.Combine(System.Environment.CurrentDirectory, assemblyName));
            }
    
            public void Register<TInterface>(string fullTypeName)
            {
                instances.Add(typeof (TInterface), EntityPtr.ToInstance<Object>(appdomain.CreateInstance(fullTypeName)));
            }
    
            public TInterface Resolve<TInterface>()
            {
                return (TInterface)(instances[typeof (TInterface)]);
            }
    
            public void Dispose()
            {
                appdomain.Dispose();
            }
        }
    

    И последнее – использующий код:
            static void Main(string[] args)
            {
                using (var container = new Container("library.dll"))
                {
                    container.Register<IServiceProvider>("IocLibrary.Implementation");
                    var serviceProvider = container.Resolve<IServiceProvider>();
    
                    Console.WriteLine("calling method without proxy: {0}", serviceProvider);
                    Console.WriteLine("Current domain assemblies: {0}",
                                      string.Join(",  ", AppDomain.CurrentDomain.GetAssemblies().Select(asm => asm.GetName().Name).ToArray()));
                }
            }
    



Выводы


Как говорится, сделать отгружаемые типы нельзя, но если хочется, то можно. Необходимо просто как-то передать между доменами указатель на объект и им можно будет на равных правах пользоваться.
Минус способа только один: мы не имеем права использовать объекты после выгрузки сборки. Это минус по одной простой причине: надо дополнительно контролировать порядок отгрузки и потери ссылки на объекты. Но, в общем случае это не проблема =)
Теги:
Хабы:
+13
Комментарии22

Публикации

Информация

Сайт
clrium.ru
Дата регистрации
Дата основания
Численность
1 человек (только я)
Местоположение
Россия
Представитель
Stanislav Sidristij

Истории