Вот и подходит двухнедельный отдых к концу, а вместе с ним и накопившиеся темы с предыдущей встречи сообщества .Net разработчиков на CLRium. Эта серия постов примерно показывает уровень, с которым мы раскрываем темы на этом мастер-классе, а что будет во второй встрече, можно почитать по ссылке: CLRium #2.
Ручное клонирование потока. Когда Assembler + C# или Java = Love
Изменение кода системных сборок или «утечка» .Net Framework 5.0
Как работает декомпиляция в .Net или Java на примере .Net
Продолжаем кромсать CLR: пул объектов .Net вне куч SOH/LOH
Снимаем дамп объектов с памяти .Net приложения
Ссылка на проект в 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())); } }
Выводы
Как говорится, сделать отгружаемые типы нельзя, но если хочется, то можно. Необходимо просто как-то передать между доменами указатель на объект и им можно будет на равных правах пользоваться.
Минус способа только один: мы не имеем права использовать объекты после выгрузки сборки. Это минус по одной простой причине: надо дополнительно контролировать порядок отгрузки и потери ссылки на объекты. Но, в общем случае это не проблема =)
Автор: sidristij