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

в 13:47, , рубрики: .net, clrium, Блог компании Luxoft, ненормальное программирование, упрлс

Вот и подходит двухнедельный отдых к концу, а вместе с ним и накопившиеся темы с предыдущей встречи сообщества .Net разработчиков на CLRium. Эта серия постов примерно показывает уровень, с которым мы раскрываем темы на этом мастер-классе, а что будет во второй встрече, можно почитать по ссылке: CLRium #2.

Ссылка на проект в GitHub: Делаем отгружаемые сборки: взаимодействуем между доменами без маршаллинга - 7 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()));
                }
            }
    

Выводы

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

Автор: sidristij

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js