NetUnload в AutoCAD

Состояние: завершена

Введение

    В этом разделе я хочу рассмотреть такую тему, как возможность выгрузки из AutoCAD загруженной ранее сборки. Мне интересна эта тема, поскольку на мой взгляд команда NETUNLOAD должна присутствовать в AutoCAD.

    По умолчанию в AutoCAD все сборки загружаются в один и тот же домен (AppDomain), которому Autodesk назначил имя "DefaultDomain". Такой подход делает невозможным выгрузку подгруженных с помощью команды NETLOAD модулей, поскольку в .Net нельзя выгрузить конкретную сборку (именно поэтому в AutoCAD на сегодняшний день отсутствует команда NETUNLOAD) - на эту тему можно почитать здесь. Для того, чтобы реализовать такую возможность в .NET, следует создать дополнительный домен (AppDomain) и загружать нужный набор сборок в него, поскольку класс AppDomain имеет статический метод Unload, с помощью которого можно выгружать домены. При выгрузке домена выгружаются и все сборки, загруженные в него. Т.о. теоретически, если под каждый .net-модуль, загружаемый в AutoCAD создавать свой отдельный домен, то такие модули можно будет при необходимости и выгружать.

    На тему работы с доменами я почитал книгу Джеффри Рихтера "CLR via C#. Программирование на платформе Microsoft .NET Framework 2.0" главу 21 - "Хостинг CLR и домены приложения (AppDomains)". В разделе "Доступ к объектам из другого AppDomain" Рихтер пишет, что для возможности такого доступа класс, объект которого будет передаваться из одного домена в другой, должен быть либо унаследованным от класса MarshalByRefObject - в этом случае объект будет передаваться по ссылке (на самом деле будет создаваться прокси), либо этот класс должен быть помечен атрибутом Serializable - в этом случае объект будет передаваться по значению (на самом деле так же будет создаваться прокси, но это уже будет экземпляр класса MarshalByValType). Если эти условия не соблюдаются - передать объект или ссылку на него из одного домена в другой не удастся... На RSDN имеется хорошая статья по AppDomain.   

    Получается, что если в .Net API AutoCAD класы не унаследованы от MarshalByRefObject, или не помечены атрибутом Serializable, то передавать экземпляры таких классов между доменами не удастся... Класс Autodesk.AutoCAD.ApplicationServices.Application этим условиям не удовлетворяет, однако классы Document, Editor, Database, Entity, DBObject и даже Transaction унаследованы от MarshalByRefObject (другие классы я не проверял). Иными словами - надежда на возможность использования доменов в AutoCAD, как я понимаю - существует...

Дополнительная информация

    Оригинал:
Issue

Like NETLOAD, is there a NETUNLOAD?

Solution
No, there isn't. DotNET does not have the concept of unloading an assembly. We would need to host your assembly in a separate appdomain and shut down the appdomain to unload your assembly. This was deemed too much hasle for little gain for now. (We may do this to provide better security and versioning guarantees in the future)

Note that you can do this if you want yourself. You would need to create a bootstrapper assembly that defines 2 commands: MyNetload and MyNetUnload. MyNetload would need to create a new AppDomain and load the user specified assembly into this appdomain. MyNetUnload could then unload the AppDomain effectively unloading the user's assembly.

Мой перевод:

Проблема

Подобно NETLOAD, существует ли NETUNLOAD?


Решение
Нет, не существует. У DotNET нет такого понятия, как "выгрузить сборку". Для того, чтобы иметь возможность выгрузить вашу сборку, мы должны были бы разместить её в отдельном AppDomain и затем, когда вам это потребуется - завершить работу этого AppDomain. Мы считаем это слишком трудоёмким в реализации ради предоставления вам небольшого удобства. (В будущем мы можем реализовать это, чтобы обеспечить лучшую безопасность и гарантии управления версиями).

Заметьте, что Вы сами можете реализовать это для себя, если захотите. В этом случае Вам следовало бы создать загрузочную сборку, в которой было бы определено две команды: MyNetload и MyNetUnload. MyNetload должен был бы создать новый AppDomain и загружать в него сборку, указанную пользователем. MyNetUnload должен был бы выгружать AppDomain, тем самым эффективно выгружая все сборки, загруженные пользователем в этот домен.

    Я написал кое-кому письмо, попросив показать пример того, как в AutoCAD правильно использовать домены в работе с дополнительными .Net-модулями. На свой запрос получил такой ответ:
Hi Andrey,
    Below is an attempt I did a while ago to load an AutoCAD .Net assembly in a separate custom AppDomain.
I was able to get it to load and call a method of the loaded assembly across domains, unfortunately when unloading the custom AppDomain and closing AutoCAD it will crash.

    I consulted the development team who confirmed that some work was to do in AutoCAD core .Net functionalities in order to be able to achieve this workflow. At the moment there are some global variables (cross domains) which will prevent that code to work properly. Although problems seem to arise only during close up of AutoCAD.

    You may be interested by that piece of code so far. I also gathered links you may find helpful. You will be able to find much more information on Microsoft side concerning AppDomains.

    I Hope it helps.

http://bytes.com/topic/c-sharp/answers/251643-good-old-assembly-unload-doesnt-exist
http://bytes.com/topic/c-sharp/answers/474227-load-unload-assembly-system-appdomain

    Мой перевод (со значительными исправлениями от Александра Ривилиса):

Привет Андрей,
    Ниже попытка, которую я сделал, для загрузки AutoCAD .NET-сборки в отдельный домен (AppDomain). Мне удалось загрузить её и вызвать метод из загруженной сборки через домены, однако после выгрузки домена и завершения AutoCAD, он "крашится".

    Я консультировался с командой разработчиков и они подтвердили, что для того, чтобы достичь нужной функциональности (т.е. возможности загружать/выгружать сборки) необходимы изменения в .NET ядре AutoCAD. В настоящее время есть некоторые глобальные переменные (используемые во всех доменах), которые мешают тому, чтобы этот код работал должным образом. Хотя проблемы, кажется, возникают только во время, закрытия AutoCAD.

    Возможно вам будет интересна та часть кода. Я также собрал ссылки, которые Вы можете счесть полезным. На сайте Microsoft вы сможете найти значительно больше информации относительно AppDomains.


Я Надеюсь, что это поможет.

Александр Ривилис:
    Я думаю, что им нужно в ядре отслеживать события загрузки/выгрузки домена и в основном домене в случае выгрузку чистить переменные, которые они создают при загрузке.

Далее я привожу код, который находился в этом письме (комментарии сразу перевожу, чтобы не дублировать код дважды):

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Reflection;
   6:   
   7:  namespace DotnetLoader {
   8:      public partial class AppDomainLoader {
   9:          AppDomain _Domain;
  10:   
  11:          AssemblyProxy _AsmProxy;
  12:   
  13:          private void LoadAsmInAppDomain(string asmPath) {
  14:              AppDomainSetup appSetup = new AppDomainSetup();
  15:              _Domain = AppDomain.CreateDomain("MyAppDomain", null, appSetup);
  16:              _Domain.DoCallBack(LoaderCallback);
  17:              _Domain.AssemblyResolve += new ResolveEventHandler(AssemblyResolve);
  18:   
  19:              AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
  20:              _AsmProxy = AssemblyProxy.CreateProxy(_Domain, asmPath);
  21:          }
  22:   
  23:          private void LoaderCallback() {
  24:              //_Asssembly = Assembly.LoadFrom(_AsmPath);
  25:          }
  26:   
  27:          Assembly AssemblyResolve(object sender, ResolveEventArgs args) {
  28:              //Этот обработчик вызывается только когда общеязыковая среда выполнения (CLR) безуспешно пытается связаться с нужной сборкой.
  29:   
  30:              //Получаем список сборок, на которые имеются ссылки в массиве AssemblyName.
  31:              string strTempAssmbPath = "";
  32:              Assembly objExecutingAssemblies = Assembly.GetExecutingAssembly();
  33:              AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
  34:   
  35:              //В цикле проходим по массиву имен сборок, на которые имеются ссылки
  36:              foreach (AssemblyName strAssmbName in arrReferencedAssmbNames) {
  37:                  //Проверяем имена сборок, которые сгенерировали событие "AssemblyResolve".
  38:                  if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(","))) {
  39:                      //Собираем полный путь к сборке, которая должна быть загружена.                       
  40:                      strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
  41:                      break;
  42:                  }
  43:              }
  44:   
  45:              //Загружаем сборку из указанного пути.                           
  46:              Assembly MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
  47:   
  48:              //Возвращаем загруженную сборку.
  49:              return MyAssembly;
  50:          }
  51:   
  52:          void UnloadAppDomain() {
  53:              _AsmProxy = null;
  54:              AppDomain.Unload(_Domain);
  55:          }
  56:      }
  57:   
  58:      class AssemblyProxy : MarshalByRefObject {
  59:          private AppDomain _Domain;
  60:          private Assembly _Assembly;
  61:          private List<TypeProxy> _Types;
  62:   
  63:          public static AssemblyProxy CreateProxy(AppDomain domain, string asmPath) {
  64:              Assembly asm = Assembly.GetExecutingAssembly();
  65:              string TypeName = typeof(AssemblyProxy).FullName;
  66:              AssemblyProxy proxy = domain.CreateInstanceAndUnwrap(asm.FullName, TypeName) as AssemblyProxy;
  67:              proxy.LoadAssembly(asmPath);
  68:              proxy.LoadAssemblyInfos();
  69:              return proxy;
  70:          }
  71:   
  72:          private bool LoadAssembly(string asmPath) {
  73:              System.Security.Policy.Evidence evidence = new System.Security.Policy.Evidence();
  74:              _Assembly = Assembly.LoadFile(asmPath, evidence);
  75:              return true;
  76:          }
  77:   
  78:          private void LoadAssemblyInfos() {
  79:              Type[] Types = _Assembly.GetExportedTypes();
  80:              _Types = new List<TypeProxy>();
  81:              foreach (Type Type in Types) {
  82:                  _Types.Add(new TypeProxy(Type));
  83:              }
  84:          }
  85:   
  86:          public TypeProxy[] GetExportedTypes() {
  87:              return _Types.ToArray();
  88:          }
  89:   
  90:          public void InvokeMethod(TypeProxy Type, MethodInfoProxy MethodInfo) {
  91:              object instance = _Assembly.CreateInstance(Type.FullName);
  92:              MethodInfo.Invoke(instance);
  93:          }
  94:      }
  95:   
  96:      class TypeProxy : MarshalByRefObject {
  97:          private Type _Type;
  98:          private List<MethodInfoProxy> _Methods;
  99:          public TypeProxy(Type Type) {
 100:              _Type = Type;
 101:              _Methods = new List<MethodInfoProxy>();
 102:              foreach (MethodInfo method in _Type.GetMethods()) {
 103:                  _Methods.Add(new MethodInfoProxy(method));
 104:              }
 105:          }
 106:   
 107:          public string Name {
 108:              get {
 109:                  return _Type.Name;
 110:              }
 111:          }
 112:   
 113:          public string FullName {
 114:              get {
 115:                  return _Type.FullName;
 116:              }
 117:          }
 118:   
 119:          public MethodInfoProxy[] GetMethods() {
 120:              return _Methods.ToArray();
 121:          }
 122:      }
 123:   
 124:      class MethodInfoProxy : MarshalByRefObject {
 125:          private MethodInfo _MethodInfo;
 126:   
 127:          public MethodInfoProxy(MethodInfo MethodInfo) {
 128:              _MethodInfo = MethodInfo;
 129:          }
 130:   
 131:          public string Name {
 132:              get {
 133:                  return _MethodInfo.Name;
 134:              }
 135:          }
 136:   
 137:          public void Invoke(object instance) {
 138:              object ret = _MethodInfo.Invoke(instance, BindingFlags.InvokeMethod, null, null, null);
 139:          }
 140:   
 141:          public void GetAttributes() {
 142:              object[] attributes = _MethodInfo.GetCustomAttributes(true);
 143:          }
 144:      }
 145:  }

Заключение

    Изначально, до получения выше указанного письма, я пытался загружать/выгружать домены и выполнять в них некоторый код (что-то даже получалось, но так, по мелочи - см. ниже "Мои попытки использования доменов в AutoCAD"). Порой получал Fatal error (причём на выполнении одного и того же кода ошибка могла произойти, а могла не появляться). После получения этого письма и редакции моего "перевода" Александром Ривилисом я делаю вывод, что заморачиваться с доменами в AutoCAD пока рано (придётся подождать, когда соответствующие изменения будут внесены разработчиками в ядро самого AutoCAD, дабы работа с доменами была стабильной). Ниже привожу свои попытки использования доменов в AutoCAD (ежели кому будет интересно)...

Мои попытки использования доменов в AutoCAD

    До того, как мне ответили выше указанным письмом, я так же пытался самостоятельно найти решение данного вопроса. Я написал тестовый .Net-плагин AutoCAD, который в процессе своей работы создавал отдельный домен (AppDomain) и загружал туда различные сборки (Assembly). На консоль AutoCAD выводилась информация о том, в какой домен какие сборки загружены... В коде своего плагина я передавал в созданный мною дополнительный домен на исполнение произвольный код в виде лямбда-выражений, которые преобразовывались в делегат типа CrossAppDomainDelegate... Однако, к сожалению, работает далеко не всё (это обусловлено тем, что я пока плохо разбираюсь в том, как правильно работать с AppDomain). Много времени я потратил на разбирательство с тем, как заставить домены и сборки искать для загрузки dll-файлы именно там, где мне это нужно (а не по определённым подкаталогам основной директории приложения)... Эта проблема, как оказалось, решается просто - нужно обработать событие AssemblyResolve. С выгрузкой домена у меня никаких проблем не возникало.

    Далее привожу свой код, с помощью которого я пытаюсь разобраться в теме...

    Для начала я написал тестовую библиотеку, в которой определил несколько команд AutoCAD (код показывать не буду, т.к. он весьма прост - методы выводят на консоль AutoCAD различные сообщения для того, чтобы визуально можно было определить была ли запущена команда и если была, то какая).
    Затем  я сел писать сбоку, задача которой была создавать домен, загружать в него нужные сборки, выполнять в нём произвольный код, а так же вызывать из него код загруженных в него модулей (например методы, определяющие команды AutoCAD). 

Проблемы

    Первая проблема, с которой я столкнулся - как заставить домен искать для загрузки ресурсы именно там, где мне это нужно. По умолчанию ресурсы ищутся по определённым правилам - кратко напомню их... Предположим что приложение называется MyApp, а загрузить требуется файл MyLib.dll. Поиск этого файла будет осуществляться в таком порядке:
  1. MyApp\MyLib.dll
  2. MyApp\MyLib\MyLib.dll
  3. MyApp\firstPrivatePath\MyLib.dll
  4. MyApp\firstPrivatePath\MyLib\MyLib.dll
  5. MyApp\secondPrivatePath\MyLib.dll
  6. MyApp\secondPrivatePath\MyLib\MyLib.dll
    В приведённых выше примерах firstPrivatePath и secondPrivatePath - это имена подкаталогов основного каталога, в которых следует производить поиск. Эти имена указываются в конфигурационном файле приложения (разделяются друг от друга знаком ";"):
   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <runtime>
   4:      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
   5:        <probing privatePath="firstPrivatePath;secondPrivatePath"/>
   6:      </assemblyBinding>
   7:    </runtime>
   8:  </configuration>
    Загружен будет первый же найденный файл. В том случае если dll-файл не будет найден - возникает событие AssemblyResolve, в обработчике которого программист может взять на себя управлением поиска ресурса.
    Однако .net-плагины AutoCAD не являются самостоятельными приложениями и поэтому CLR для аналитики использует файл основного приложения - т.е. файл acad.exe.config, а это совсем не то, что мне нужно... Назначить другой конфирурационный файл домену можно, если при его создании использовать экземпляр класса AppDomainSetup. Однако после того, как домен создан, некоторые свойства уже не удаётся изменить, несмотря на то, что они доступны для изменения и никаких ошибок не возникает (изменения попросту игнорируются)...

    Я написал файл Settings.xml, в котором указал, в каких каталогах следует выполнять поиск ресурсов, ежели они не были найдены изначально:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <Settings>  
   3:    <SearchPaths>    
   4:      <Path>D:\MD\Visual Studio 2010\DomainUseSample\DomainUseSample\bin\Debug</Path>
   5:      <Path>D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug</Path>    
   6:    </SearchPaths>
   7:  </Settings>

    После создания домена мне не удаётся переназначить ему каталог приложения - эти изменения попросту игнорируются и свойство продолжает содержать значение, полученное при инициализации домена:

   1:  //Я пытаюсь назначить новый каталог приложения, но по факту никаких изменений не происходит - каталог остаётся прежним!!!
   2:  dom.SetDynamicBase(@"D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug");                
   3:  //или так:
   4:  dom.SetupInformation.ApplicationBase = @"D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug";

    Ощущение такое, что метод SetDynamicBase и аксессор set свойства ApplicationBase попросту не содержат в себе кода, выполняющего указанную мною операцию (тогда какой смысл их наличия???)...
    Код тестового примера содержит в своём составе большое количество комментариев, поясняющих что, где и как я делаю, а так же то, где какие проблемы возникают...

Моя попытка решения

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using acad = Autodesk.AutoCAD.ApplicationServices.Application;
   6:  using Autodesk.AutoCAD.ApplicationServices;
   7:  using Autodesk.AutoCAD.DatabaseServices;
   8:  using Autodesk.AutoCAD.EditorInput;
   9:  using Autodesk.AutoCAD.Runtime;
  10:  using System.Reflection;
  11:  using System.IO;
  12:  using System.Security.Policy;
  13:  using System.Security;
  14:  using System.Security.Permissions;
  15:  using System.Threading;
  16:  using System.Xml.Linq;
  17:  using Autodesk.AutoCAD.Interop;
  18:  using System.Windows.Forms;
  19:  using System.Diagnostics;
  20:   
  21:  [assembly: CommandClass(typeof(DomainUseSample.Class1))]
  22:   
  23:  namespace DomainUseSample {
  24:      [Serializable]
  25:      public sealed class Class1 : MarshalByRefObject {
  26:          static AppDomain dom;
  27:          static Editor ed;
  28:          static ResolveEventHandler evh;
  29:          /// <summary>
  30:          /// Создать дополнительный домен
  31:          /// </summary>
  32:          [CommandMethod("bush", "dload", CommandFlags.Session)]
  33:          public void MyNetload() {
  34:              ed = acad.DocumentManager.MdiActiveDocument.Editor;
  35:              if (dom != null) {
  36:                  ed.WriteMessage("Домен уже существует.\n");
  37:                  return;
  38:              }
  39:              AppDomain curDomain = Thread.GetDomain();
  40:              evh = new ResolveEventHandler(LoadFromSomeFolder);
  41:              curDomain.AssemblyResolve += evh;
  42:              
  43:              //Вывожу на консоль AutoCAD информацию о сборках, загруженных в основной (текущий) домен
  44:              DomAsmsPrint(curDomain);
  45:   
  46:              AppDomainSetup aps = new AppDomainSetup();
  47:              //aps.ApplicationName = "DomainUseSample";
  48:              aps.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  49:   
  50:              dom = AppDomain.CreateDomain("Domain #2", null, aps);
  51:   
  52:              //Указываю, в каких каталогах следует искать сборки, если они не были найдены в
  53:              //основном каталоге программы и его определённых подкаталогах (прописанных в config-файле
  54:              //в переменной probing.
  55:              dom.AssemblyResolve += new ResolveEventHandler(LoadFromSomeFolder);
  56:   
  57:              //Попытка зарегистрировать событие непосредственно в теле нового домена:
  58:              //dom.DoCallBack(() => {
  59:              //    AppDomain d = AppDomain.CurrentDomain;
  60:              //    d.AssemblyResolve += (object sender, ResolveEventArgs args) => {
  61:              //        //список каталогов, в которых следует искать dll-ки.
  62:              //        List<string> list = new List<string>();
  63:              //        //Каталог, в котором находится текущий исполняемый модуль
  64:              //        string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  65:              //        list.Add(folderPath);
  66:              //        //Основной каталог AutoCAD
  67:              //        list.Add(((AcadApplication)acad.AcadApplication).Path);
  68:   
  69:              //        //xml-файл, в котором перечислены дополнительные каталоги поиска.
  70:              //        XElement xml = XElement.Load(@"D:\MD\Visual Studio 2010\DomainUseSample\DomainUseSample\Settings.xml");
  71:   
  72:              //        foreach (XElement item in xml.Element("SearchPaths").Elements("Path"))
  73:              //            list.Add(item.Value.Trim());
  74:              //        //Теперь в цикле ищу нужный файл по всем указанным каталогам и загружаю первое совпадение.
  75:              //        foreach (string item in list) {
  76:              //            string parentPath = item;
  77:              //            string assemblyPath = Path.Combine(parentPath, args.Name.Split(',')[0].Trim() + ".dll");
  78:              //            if (File.Exists(assemblyPath)) {
  79:              //                Assembly assembly = Assembly.LoadFrom(assemblyPath);
  80:              //                return assembly;
  81:              //            }
  82:              //        }
  83:              //        //Если ничего найти не удалось - генерирую исключение
  84:              //        throw new System.Exception("Не удалось найти файл сборки");
  85:              //    };
  86:              //});                        
  87:              
  88:              ed.WriteMessage(string.Format("Создан новый домен с именем \"{0}\"\n", dom.FriendlyName));
  89:              //Теперь в созданном мною домене выполню некоторый код
  90:              try {
  91:                  //Загружаю в дополнительный домен новую сборку
  92:                  dom.DoCallBack(() => Assembly.LoadFrom(@"D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug\CmdSample.dll"));
  93:                  
  94:                  //Загружаю ещё одну сборку, но уже из GAC (чтобы затем показать MessageBox)...
  95:                  dom.DoCallBack(() => Assembly.LoadFrom(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll"));
  96:   
  97:                  //Загружаю в дополнительный домен нужные сборки AutoCAD
  98:                  dom.DoCallBack(() => Assembly.LoadFrom(@"C:\Program Files\AutoCAD 2009\AcDbMgd.dll"));
  99:                  dom.DoCallBack(() => Assembly.LoadFrom(@"C:\Program Files\AutoCAD 2009\AcMgd.dll"));
 100:   
 101:                  //Вывожу на консоль AutoCAD информацию о сборках, загруженных в дополнительном домене
 102:                  DomAsmsPrint(dom);
 103:   
 104:                  //А ТЕПЕРЬ БУДУ ПЫТАТЬСЯ В ДОПОЛНИТЕЛЬНОМ ДОМЕНЕ ВЫПОЛНИТЬ НЕКОТОРЫЙ КОД...
 105:   
 106:                  //ЭТОТ КОД ВЫПОЛНЯЕТСЯ УСПЕШНО!
 107:                  //Из нового домена создаю текстовый файл и записываю в его некоторую информацию (этот код отрабатывает нормально)
 108:                  dom.DoCallBack(() => {
 109:                      using (StreamWriter sw = File.CreateText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MyTempFile.txt"))) {
 110:                          sw.WriteLine(string.Format("Этот файл создан и заполнен текстом из домена \"{0}\"", AppDomain.CurrentDomain.FriendlyName));
 111:                          sw.Flush();
 112:                          sw.Close();
 113:                      }
 114:   
 115:                      //Создаю и открываю НЕМОДАЛЬНОЕ окно
 116:                      Form frm = new Form() {
 117:                          Text =  string.Format("Это НЕМОДАЛЬНОЕ диалоговое окно создано и открыто в домене \"{0}\"",
 118:                              AppDomain.CurrentDomain.FriendlyName), Width = 700, Height = 150
 119:                      };
 120:                      //Окно откроется, но есть проблема: временами, при попытке что-то сделать с окном (помимо закрытия) может 
 121:                      //возникать Fatal error, а может и не возникать (закономерности не выявил)...
 122:                      frm.Show();
 123:   
 124:                      //Пробую создать МОДАЛЬНОЕ окно...
 125:                      //Это окно почему-то не видно, но тем не менее наличие этого кода не позволяет закрыть немодальное окно, созданное в коде выше. 
 126:                      //Как вариант - приходится в AutoCAD вызывать команду dunload (определённую в этом же классе ниже), чтобы выгрузить сборку. 
 127:                      //Причём модальность окна не распространяется на AutoCAD (т.е. имеется доступ к командной строке и т.п.).
 128:                      //frm = new Form() {
 129:                      //    Text =  string.Format("Это МОДАЛЬНОЕ диалоговое окно создано и открыто в домене \"{0}\"",
 130:                      //        AppDomain.CurrentDomain.FriendlyName), Width = 700, Height = 150, Left = 50, Top = 50
 131:                      //};
 132:                      //frm.ShowDialog();
 133:   
 134:                  });//В результате работы этого кода создаётся текстовый файл, в котором записана строка: "Это сообщение отправлено из домена "Domain #2"".
 135:   
 136:                  //ЭТОТ КОД ПРИВЕДЁТ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ!
 137:                  //ТЕКСТ ИСКЛЮЧЕНИЯ:
 138:                  //Возникло исключение: "Type is not resolved for member '<CrtImplementationDetails>.ModuleLoadException,msvcm80, 
 139:                  //Version=8.0.50727.4027, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'."
 140:                  //
 141:                  //dom.DoCallBack(()=> MessageBox.Show(string.Format("Это сообщение отправлено из домена \"{0}\"", AppDomain.CurrentDomain.FriendlyName)));
 142:   
 143:                  //dom.SetDynamicBase(@"C:\Program Files\AutoCAD 2009");
 144:   
 145:                  //ЭТОТ КОД ПРИВЕДЁТ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ!
 146:                  //ТЕКСТ ИСКЛЮЧЕНИЯ:
 147:                  //Возникло исключение: "Could not load file or assembly 'acmgd, Version=17.2.0.0, 
 148:                  //Culture=neutral, PublicKeyToken=null' or one of its dependencies. An error 
 149:                  //relating to serialization occurred. (Exception from HRESULT: 0x8013150C)"
 150:                  //
 151:                  //Странно, ведь я же загрузил в дополнительный домен сборки AcDbMgd и AcMgd (эта информация была выведена на консоль AutoCAD).
 152:                  //dom.DoCallBack(()=> acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("Это сообщение отправлено из домена \"{0}\"",
 153:                  //    AppDomain.CurrentDomain.FriendlyName)));
 154:   
 155:                  //ЭТОТ КОД ПРИВЕДЁТ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ!
 156:                  //ТЕКСТ ИСКЛЮЧЕНИЯ: 
 157:                  //Возникло исключение: "Could not load file or assembly 'CmdSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its 
 158:                  //dependencies. An error relating to serialization occurred. (Exception from 
 159:                  //HRESULT: 0x8013150C)"
 160:                  //
 161:                  
 162:                  //Далее я пытаюсь назначить новый каталог приложения, но по факту никаких изменений не происходит - каталог остаётся прежним!!!
 163:                  //dom.SetDynamicBase(@"D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug");                
 164:                  //dom.SetupInformation.ApplicationBase = @"D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug";
 165:   
 166:                  //ЭТОТ КОД ПРИВЕДЁТ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ!
 167:                  //ТЕКСТ ИСКЛЮЧЕНИЯ:
 168:                  //Возникло исключение: "Could not load file or assembly 'CmdSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its 
 169:                  //dependencies. An error relating to serialization occurred. (Exception from 
 170:                  //HRESULT: 0x8013150C)"
 171:                  //
 172:                  //ed.WriteMessage("Каталог приложения: \"{0}\"\n", dom.BaseDirectory);
 173:                  //dom.DoCallBack(() => {
 174:                  //    Type type = Type.GetType("CmdSample.Commands, CmdSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
 175:                  //    object x = Activator.CreateInstance(type);
 176:                  //    type.GetMethod("InstanceCommand").Invoke(x, null);
 177:                  //});
 178:              }
 179:              catch (System.Exception ex) {
 180:                  ed.WriteMessage(string.Format("Возникло исключение: \"{0}\"\n", ex.Message));
 181:                  return;
 182:              }
 183:   
 184:              //Снова смотрю состав сборок, загруженных в основной и дополнительный домены
 185:              ed.WriteMessage("##################\n");
 186:              ed.WriteMessage(string.Format("Теперь снова проверяю содержимое обоих доменов...\n", dom.FriendlyName));
 187:              ed.WriteMessage("##################\n");
 188:              //Информация по основному домену
 189:              DomAsmsPrint(curDomain);
 190:              //Информация по дополнительному домену
 191:              DomAsmsPrint(dom);
 192:   
 193:   
 194:   
 195:              ed.WriteMessage("Загрузка выполнена успешно.\n");
 196:          }
 197:   
 198:          /// <summary>
 199:          /// Вывести на консоль AutoCAD информацию о домене
 200:          /// </summary>
 201:          /// <param name="dom1"></param>
 202:          private static void DomAsmsPrint(AppDomain dom1) {
 203:              ed = acad.DocumentManager.MdiActiveDocument.Editor;
 204:              ed.WriteMessage(string.Format("ПЕРЕЧЕНЬ СБОРОК, ЗАГРУЖЕННЫХ В ДОМЕН \"{0}\":\n", dom1.FriendlyName));
 205:              ed.WriteMessage("**************\n");
 206:              foreach (Assembly item in dom1.GetAssemblies()) {
 207:                  ed.WriteMessage(string.Format("{0}\n", item.Location));
 208:              }
 209:              ed.WriteMessage("**************\n");
 210:          }
 211:   
 212:          /// <summary>
 213:          /// Выгрузить дополнительный домен
 214:          /// </summary>
 215:          [CommandMethod("bush", "dunload", CommandFlags.Session)]
 216:          public void MyNetUnload() {
 217:              ed = acad.DocumentManager.MdiActiveDocument.Editor;
 218:              if (dom == null) {
 219:                  ed.WriteMessage("Дополнительный домен ещё не создан! Сначала запустите команду dload.\n");
 220:                  return;
 221:              }
 222:              try {
 223:                  dom.ResourceResolve -= evh;
 224:                  AppDomain.Unload(dom);
 225:                  dom = null;
 226:              }
 227:              catch (System.Exception ex) {
 228:                  ed.WriteMessage(string.Format("Сообщение об ошибке: \"{0}\"\n", ex.Message));
 229:                  return;
 230:              }
 231:              ed.WriteMessage("Выгрузка метода прошла успешно.\n");
 232:          }
 233:   
 234:          /// <summary>
 235:          /// Найти и загрузить нужную сборку. 
 236:          /// </summary>
 237:          /// <param name="sender">Отправитель</param>
 238:          /// <param name="args">Аргументы</param>
 239:          /// <returns>Возвращается загруженная сборка</returns>
 240:          Assembly LoadFromSomeFolder(object sender, ResolveEventArgs args) {
 241:              //список каталогов, в которых следует искать dll-ки.
 242:              List<string> list = new List<string>();
 243:              //Каталог, в котором находится текущий исполняемый модуль
 244:              string folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
 245:              list.Add(folderPath);
 246:              //Основной каталог AutoCAD
 247:              list.Add(((AcadApplication)acad.AcadApplication).Path);
 248:   
 249:              //xml-файл, в котором перечислены дополнительные каталоги поиска.
 250:              XElement xml = XElement.Load(Path.Combine(folderPath, "Settings.xml"));
 251:   
 252:              foreach (XElement item in xml.Element("SearchPaths").Elements("Path"))
 253:                  list.Add(item.Value.Trim());
 254:              //Теперь в цикле ищу нужный файл по всем указанным каталогам и загружаю первое совпадение.
 255:              foreach (string item in list) {
 256:                  string parentPath = item;
 257:                  string assemblyPath = Path.Combine(parentPath, args.Name.Split(',')[0].Trim() + ".dll");
 258:                  if (File.Exists(assemblyPath)) {
 259:                      Assembly assembly = Assembly.LoadFrom(assemblyPath);
 260:                      return assembly;
 261:                  }
 262:              }
 263:              //Если ничего найти не удалось - генерирую исключение
 264:              throw new System.Exception("Не удалось найти файл сборки");
 265:          }
 266:      }
 267:  }

    В результате работы кода дополнительный домен создаёт текстовый файл с сообщением, создаёт и показывает новое окошко в заголовке которого так же содержится соответствующий текст. Кроме этого я вывожу на консоль AutoCAD информацию о том, в какой домен какие сборки загружены (правда делаю это пока, к сожалению, не из дополнительного домена). Если при открытом диалоговом окошке вызвать команду dunload - домен будет выгружен и, как следствие - окошко будет закрыто.
Comments