Архив за месяц: Март 2014

Баг BlockTableRecord.HasAttributeDefinitions.

На форуме adn-cis.org недавно появилось сообщение о том, что BlockTableRecord.HasAttributeDefinitions возвращает неверное значение для тех записей, из которых определения атрибутов на самом деле были ранее удалены. Наличие данного бага было подтверждено и заявка отправлена в ADN. Соответственно, придётся писать свою, корректную реализацию данного функционала.

По обозначенной выше ссылке проблема обозначалась следующим образом (отредактированная цитата):
1. Создаем определение блока c одним произвольным объектом (например с полилинией).
2. Добавляем определение атрибута в наше 
определение блока.
3. Видим, что свойство BlockTableRecord.HasAttributeDefinitions равно true, как и полагается.
4. Итерацией проходим по объектам нашей BlockTableRecord и видим, что их два: полилиния и определение атрибута.
5. Теперь удаляем ранее добавленное определение атрибута.
6.Итерацией проходим по объектам нашей BlockTableRecord и видим лишь один объект (как и полагается): полилинию.
7. Теперь смотрим свойство BlockTableRecord.HasAttributeDefinitions и видим, что оно равно true вместо ожидаемого false.
Свою версию обозначенного выше функционала реализуем в виде метода расширения, которому присвоим имя HasAttDefs:

   1:  // BlockTableRecordExtentions.cs
   2:  // © Andrey Bushman, 2014
   3:  // Extention methods for the BlockTableRecord class
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Linq;
   7:  using System.Text;
   8:   
   9:  /// В целях переносимости кода и отсутствия в псевдонимах наименований
  10:  /// конкретного САПР, целесообразней формировать псевдонимы в 
  11:  /// нейтральной форме, например: cad, вместо acad, Ap вместо AcAp, Db 
  12:  /// вместо AcDb и т.д. Построенная таким способом система наименований
  13:  /// будет более удобной программисту, портирующему ваш код под другую 
  14:  /// САПР. Ниже приведён вариант определений таких нейтральных псевдонимов
  15:  /// под некоторый набор различных САПР.
  16:  #if AUTOCAD
  17:  using cad = Autodesk.AutoCAD.ApplicationServices.Application;
  18:  using Ap = Autodesk.AutoCAD.ApplicationServices;
  19:  using Db = Autodesk.AutoCAD.DatabaseServices;
  20:  using Ed = Autodesk.AutoCAD.EditorInput;
  21:  using Gm = Autodesk.AutoCAD.Geometry;
  22:  using Rt = Autodesk.AutoCAD.Runtime;
  23:  #elif BRICSCAD
  24:  using cad = Bricscad.ApplicationServices.Application;
  25:  using Ap = Bricscad.ApplicationServices;
  26:  using Db = Teigha.DatabaseServices;
  27:  using Ed = Bricscad.EditorInput;
  28:  using Gm = Teigha.Geometry;
  29:  using Rt = Bricscad.Runtime;
  30:  #elif NANOCAD
  31:  using cad = HostMgd.ApplicationServices.Application;
  32:  using Ap = HostMgd.ApplicationServices;
  33:  using Db = Teigha.DatabaseServices;
  34:  using Ed = HostMgd.EditorInput;
  35:  using Gm = Teigha.Geometry;
  36:  using Rt = Teigha.Runtime; 
  37:  #endif
  38:   
  39:  namespace Bushman.CAD.Extentions {
  40:      /// <summary>
  41:      /// Методы расширения для экземпляров класса BlockTableRecord
  42:      /// </summary>
  43:      public static class BlockTableRecordExtentions {
  44:          /// <summary>
  45:          /// Данный метод проверяет наличие экземпляров <c>Db.AttributeDefinition</c> в
  46:          /// составе объекта <c>BlockTableRecord</c> и представляет собой замену методу
  47:          /// <c>BlockTableRecord.HasAttributeDefinitions</c>, который реализован 
  48:          /// неверно - в виду этого и возникла необходимость написать корректный вариант
  49:          /// реализации. Информация о некорректной работе 
  50:          /// <c>BlockTableRecord.HasAttributeDefinitions</c> была подтверждена и
  51:          /// отправлена в ADN. Подробности на странице 
  52:          /// http://adn-cis.org/forum/index.php?topic=625.0
  53:          /// </summary>
  54:          /// <param name="btr">экземпляр <c>BlockTableRecord</c>, подлежащий проверке.</param>
  55:          /// <returns>true - в составе указанного объекта <c>BlockTableRecord</c>
  56:          /// содержатся элементы <c>Db.AttributeDefinition</c>, иначе - false.</returns>
  57:          public static Boolean HasAttDefs(this Db.BlockTableRecord btr) {
  58:              String name = Rt.RXClass.GetClass(typeof(Db.AttributeDefinition)).Name;
  59:              return btr.Cast<Db.ObjectId>().Any(n => !n.IsNull && n.IsValid
  60:                  && !n.IsErased && !n.IsEffectivelyErased && String.Equals(
  61:                  n.ObjectClass.Name, name, StringComparison.InvariantCulture));
  62:          }
  63:   
  64:  #if DEBUG
  65:          /// <summary>
  66:          /// Команда, демонструрующая некорректную работу 
  67:          /// <c>BlockTableRecord.HasAttributeDefinitions</c>
  68:          /// и корректность работы метода <c>HasAttDefs</c>.
  69:          /// За основу взят код 
  70:          /// http://adn-cis.org/forum/index.php?topic=625.msg2168#msg2168
  71:          /// </summary>
  72:          [Rt.CommandMethod("TestBlock")]
  73:          public static void TestBlock() {
  74:              Ap.Document doc = cad.DocumentManager.MdiActiveDocument;
  75:              Ed.Editor ed = doc.Editor;
  76:              Ed.PromptResult res = ed.GetString("nType name of block: ");
  77:              if (res.Status != Ed.PromptStatus.OK) return;
  78:              using (Db.Transaction tr = doc.TransactionManager.StartTransaction()) {
  79:                  Db.BlockTable bt = tr.GetObject(doc.Database.BlockTableId,
  80:                      Db.OpenMode.ForRead) as Db.BlockTable;
  81:                  if (bt != null) {
  82:                      if (bt.Has(res.StringResult)) {
  83:                          Db.BlockTableRecord btr = tr.GetObject(bt[res.StringResult],
  84:                              Db.OpenMode.ForRead) as Db.BlockTableRecord;
  85:                          if (btr != null) {
  86:                              ed.WriteMessage("nAutodesk: Block {0} has{1} attribute definitions.n",
  87:                                  res.StringResult, btr.HasAttributeDefinitions ? "" : " not");
  88:                              // Мною добавлена эта строка кода:
  89:                              ed.WriteMessage("nBushman: Block {0} has{1} attribute definitions.n",
  90:                                  res.StringResult, btr.HasAttDefs() ? "" : " not");
  91:                          }
  92:                      }
  93:                      else {
  94:                          ed.WriteMessage("nBlock {0} not found", res.StringResult);
  95:                      }
  96:                  }
  97:                  tr.Commit();
  98:              }
  99:          }
 100:  #endif
 101:      }
 102:  }

Последовательно выполняем все шаги, демонстрирующие появление проблемы и запускаем команду TestBlock:


Как видим - наша реализация работает корректно.

tlbimp.exe и целевая версия .NET Framework

ObjectARX SDK содержит некоторый набор TLB файлов. Для некоторых из них приходится генерировать управляемую обёртку "вручную" (причина указана ниже), посредством утилиты tlbimp.exe (если нужная обёртка изначально отсутствует). Обозначенная утилита содержит некоторый набор ключей для конфигурирования конечного результата. Однако одной, очень важной настройки, всё же не хватает...

На моём рабочем компьютере установлены MS Visual Studio 2005-2012 и .NET Framework 2.0 - 4.5. В первый раз я столкнулся с необходимостью прибегать к "ручному" использованию утилиты tlbimp.exe, когда в MS Visual Studio 2012 был создан проект плагина AutoCAD 2010 (целевая платформа .NET 3.5, разрядность x64), но при этом мне не далось подключить ссылки на acax18ENU.tlb и axdb18enu.tlb. (на самом деле именно эти tlb файлы нет никакой нужды подключать, т.к. вместо них нужно подключать библиотеки Interop, но в данном случае на их примере я показываю проблему, которая может возникнуть при использовании утилиты tlbimp.exe). "За кулисами", при подключении TLB файлов IDE обычно для них автоматически генерирует управляемые обёртки, но в данном случае этого не произошло:


Насколько мне известно, причиной подобной ошибки может быть некорректная регистрация COM в реестре, либо вообще отсутствие в реестре этой информации.

Та же самая ситуация для AutoCAD 2010 наблюдалась и в MS Visual Studio 2005-2010. В виду этого приходится генерировать DLL обёртку при помощи "ручного" запуска утилиты tlbimp.exe. Однако и тут не всё так красиво, как хотелось бы: к сожалению, в утилите tlbimp.exe нет возможности указать целевую версию платформы .NET, для которой следует генерировать обёртку.
    Я пытался сгенерировать обёртку для нужных мне TLB файлов из консолей разных версий MS Visual Studio следующим образом:

    tlbimp "C:SDKAutodeskAutoCADObjectARX 2010inc-x64acax18ENU.tlb" /out:"C:SDKAutodeskAutoCADObjectARX 2010inc-x64acax18ENU.Interop.dll" /namespace:AutoCAD /machine:x64

    tlbimp "C:SDKAutodeskAutoCADObjectARX 2010inc-x64axdb18enu.tlb" /out:"C:SDKAutodeskAutoCADObjectARX 2010inc-x64axdb18enu.Interop.dll" /namespace:AXDBLib /machine:x64

    Примечание:
    При подключении этих оболочек к проекту, необходимо их свойство Copy Local устанавливать в TRUE, поскольку они сгенерированы нами и будут отсутствовать в каталоге AutoCAD.

    Если использовать консоль VS 2010 или VS 2012, то обёртка будет создана для .NET 4.0. Однако в AutoCAD 2010 использует .NET 3.5, поэтому данный результат мне не подходит...

    Получить нужный результат мне удалось лишь воспользовавшись консолью VS 2008.

    На моей машинке, в составе установленных SDK

    утилиту tlbimp.exe мне удалось найти в каталогах:
    • C:Program Files (x86)Microsoft SDKsWindowsv8.1AbinNETFX 4.5.1 Tools
    • C:Program Files (x86)Microsoft SDKsWindowsv8.0AbinNETFX 4.0 Tools
    • C:Program Files (x86)Microsoft SDKsWindowsv7.0ABin
    Кроме того, утилита была найдена и в каталоге
    • C:Program Files (x86)Microsoft Visual Studio 8SDKv2.0Bin
    В обозначенных выше первых трёх каталогах, я пробовал создавать конфигурационный файл tlbimp.exe.config следующего содержимого:

    <?xml version="1.0" encoding="utf-8"?>
       <startup>
       <supportedRuntime version="v2.0.50727"/>
    </startup>


    Затем из cmd.exe, перейдя в каталог указанной утилиты, запустил такую команду:

    C:Program Files (x86)Microsoft SDKsWindowsv8.1AbinNETFX 4.5.1 Tools>tlbimp
     "D:SDKAutodeskAutoCADObjectARX 2010inc-x64acax18ENU.tlb" /out:"D:SDKAutodeskAutoCADObjectARX 2010inc-x64acax18ENU.Interop.dll" /namespace:AutoCAD /machine:x64
    Не удалось запустить приложение, поскольку его параллельная конфигурация неправильна. Дополнительные сведения содержатся в журнале событий приложений или используйте программу командной строки sxstrace.exe для получения дополнительных сведений.


    Обозначенный выше результат наблюдался для версий v8.1A и v8.0A. Для версии v7.0A в перенастройке не было необходимости, т.к. на выходе и так получается библиотека для .Net 2.0.
    Если удалить созданный мною конфигурационный файл, то для v8.1A и v8.0A операция так же проходит успешно, но на выходе, конечно же, получаем библиотеку для .Net 4.0:

    C:Program Files (x86)Microsoft SDKsWindowsv8.1AbinNETFX 4.5.1 Tools>
    C:Program Files (x86)Microsoft SDKsWindowsv8.1AbinNETFX 4.5.1 Tools>tlbimp
     "D:SDKAutodeskAutoCADObjectARX 2010inc-x64acax18ENU.tlb" /out:"D:SDKAutodeskAutoCADObjectARX 2010inc-x64acax18ENU.Interop.dll" /namespace:AutoCAD /machine:x64
    Microsoft (R) .NET Framework Type Library to Assembly Converter 4.0.30319.33440
    Copyright (C) Microsoft Corporation.  All rights reserved.

    TlbImp : warning TI3019 : Interface 'IAcadShadowDisplay' is marked as [dual], but does not derive from IDispatch. It will be converted as an IUnknown-derived interface.
    TlbImp : Type library imported to D:SDKAutodeskAutoCADObjectARX 2010inc-x64
    acax18ENU.Interop.dll


    Вывод:
    Если необходимо сгенерировать управляемые DLL обёртки на основе файлов TLB, то для разных целевых версий .NET следует использовать утилиту tlbimp.exe из разных Windows SDK:
    • SDK v7.0A - для .NET 2.0, 3.0, 3.5 SP1
    • SDK v8.1A или v8.0A - для .NET 4.0, 4.5, 4.5.1

    Об управляемых "заглушках" из ObjectARX SDK и целевой платформе (x86x64)

    Как известно, управляемые "заглушки" находятся в ObjectARX SDK в подкаталогах inc, inc-win32 и inc-x64. В идеальном варианте, хотелось бы всегда компилировать код как AnyCPU, однако не во всех случаях это возможно и порой приходится создавать сборки отдельно для x86 и отдельно для x64.

    В ObjectARX SDK 2009 каталог inc не содержит управляемых "заглушек" - они там появились лишь начиная с версии 2011. До этого же их версии находились по подкаталогам inc-win32 и inc-x64. Изначально, базовый набор "заглушек", подключаемых в управляемом плагине AutoCAD состоял из файлов AcDbMgd.dll и AcMgd.dll. Позднее, начиная с версии 2013, к этому перечню добавился файл AcCoreMgd.dll.

    Согласно заявлению сотрудников Autodesk - в том случае, если в управляемом плагине подключается "заглушка" не из каталога inc, то такой плагин следует компилировать не как AnyCPU, но отдельно под x86 и отдельно под x64при этом переподключая "заглушки" из соответствующего каталога - для каждой платформы свои "заглушки". Т.е. если "заглушки" подключаются из inc-win32, то и компилировать такой код следует только для x86. А ежели нужно компилировать для x64, то в подключенных ссылках нужно переуказывать заглушки из каталога inc-x64, вместо заглушек из inc-win32. В том же случае, если в плагине используются лишь заглушки каталога inc, то его можно компилировать как AnyCPU.

    Порой нарушение этого правила может и сойти с рук: например проект, в котором подключены "заглушки" из inc-x64, может быть откомпилирован как x86, после чего успешно работать без каких либо проблем. Или же, как вариант: разработчиком могут быть подключены заглушки не из каталога inc, с последующей компиляцией сборки как AnyCPU. Однако компания Autodesk (в лице Stephen Preston) предупреждает, что в ряде случаев (какие именно - не уточнялось), при подобном подходе могут возникать проблемы.

    Разработка приложений: виртуальные машинки и удалённая отладка

    Для того, чтобы на компьютер "с нуля" установить всё необходимое для работы программное обеспечение, нередко требуется много времени. При этом не имеет значения, физическая это машинка или же виртуальная.

    Описание проблемы 
    Для того, чтобы настроить машинку на разработку и тестирование плагинов AutoCAD версий 2009, 2010, 2011, 2012, 2013, 2014, нужно не только строго в обозначенном порядке установить их, но так же установить и соответствующие для них версии SDK. Так же необходимо установить и MS Visual Studio. В том же случае, если нужно писать плагины не только на .NET но и на C++, ситуация ещё более осложняется, поскольку и IDE придётся ставить так же несколько: для неуправляемого кода C++ каждой версии AutoCAD соответствует своя версия MS Visual Studio.
    Помимо этого, порой приходится устанавливать немалый набор дополнительного ПО: препочитаемый браузер, пакет MS Office, архиватор, программу работающую с виртуальными образами, Adobe Reader, антивирус и т.д. и т.п.



    Конечно же, гораздо удобней всё это один раз установить на виртуальную машинку и затем сделать её рабочую копию. Т.о. если текущая копия придёт в негодность, то достаточно быстро и просто можно создать новую на основе ранее созданной заготовки. Однако тут есть одно "НО"... Но что, если потребуется разрабатывать ПО не для AutoCAD, а к примеру, для Revit? Нужно ли устанавливать Revit на эту же виртуальную машинку (возможно даже несколько версий Revit), или же по аналогии создавать новую? А что, если дополнительно потребуется разрабатывать и тестировать код для nanoCAD, BricsCAD, а так же писать что-то своё, используя библиотеки ODA?

    Не стоит забывать и о том, что разрабатываемый нами код, по хорошему, следовало бы тестировать не только на Windows 7 x64, но и на Windows 7 x86, а так же, возможно, что и на Windows XP x86. Это означает, что придётся создавать такие машинки и устанавливать на них все программы, необходимые для тестирования нашего кода.

    По моему личному ощущению - если на компьютер устанавливается множество различного ПО, занимающего много места, то порой такой компьютера начинает работать ощутимо медленней, чем до этой установки.

    Общая идея

    Учитывая вышеизложенное, целесообразно отделить мух от котлет: т.е. создавать отдельно виртуальную машинку для разработки, и отдельно виртуальные машинки, предназначенные для тестирования и удалённой отладки кода.

    На машинку, предназначенную для разработки, устанавливаются только набор нужных IDE, SDK, а так же необходимый разработчику, дополнительный набор инструментов и программных библиотек. При этом сами же целевые приложения, для которых ведётся разработка, на эту машину не устанавливаются. Это означает, что на машинку разработчика не устанавливаются ни AutoCAD, ни Revit, ни nanoCAD, ни BricsCAD и т.д.

    Для тестирования и отладки же создаются и настраиваются отдельные виртуальные машинки, работающие под разными, интересующими нас операционными системами. Затем на эти машинки устанавливается набор нужных  программ, например, AutoCAD 2009 - 2014. Можно, при желании, на эту же машинку устанавливать и Revit, nanoCAD, BricsCAD и т.д., однако я всё же предпочитаю этого не делать и создавать для них отдельные виртуальные машинки, по аналогии, как это было показано для AutoCAD.



    Т.о. получается, что каждая такая машинка ориенирована на тестирование и отладку различных версий конкретного приложения. Сама же по себе отладка и тестирование выполняется удалённо, посредством установленной на клиентской машине нужной версии Remote Debugger.



    Да, безусловно, на создание таких виртуальных машинок придётся один раз потратить некоторое время. Однако, всё не так страшно, как может показаться на первый взгляд. Ниже приводится пример того, как я делал это для себя.

    Примечание
    В первый раз, обозначенная выше идея была мною реализована ещё пару-тройку лет назад, когда мой компьютер работал под управлением операционной системы Windows 7 x64, а для работы с виртуальными машинками использовался VMware. Результат был так же успешен. Около года назад (может чуть больше), я полностью перешёл дома на Linux, а вместо VMware решил использовать VirtualBox.

    Пример реализации

    Итак, мой домашний компьютер работает под управлением операционной системы Linux. Дополнительно установлен VirtualBox (для работы с виртуальными машинками).

    Собирая свой домашний физический компьютер (несколько лет назад), я заранее ориентировался на стиль разработки, который описывается мною в этой заметке: активное использование виртуальных машинок и выполнение на них удалённой отладки. Этим и обусловлены конечные параметры:
    • процессор Core i7
    • объём оперативной памяти - 24 Gb
    • пять жёстких дисков по 1Tb каждый, из них три внешних (для виртуальных машинок целиком отведён один из дисков, а для резервного копирования - ещё один)
    • видеокарта NVIDIA GeForce GTX 570

    Примечание
    К счастью, на сегодняшний день, цены на подобные составляющие являются вполне приемлемыми. 

    В результате, одновременная работа, к примеру, четырёх виртуальных машин не вызывает никаких проблем: всё работает на удивление шустро. Более того, они работают ощутимо быстрее, чем мой физический компьютер на работе. Обычно, при разработке под AutoCAD, у меня одновременно запущено сразу несколько виртуальных машинок одна та, что с IDE, а дополнительные - это машинки win7x64_acads_(work), win7x86_acads_(work) и winXPx86_acads_(work) с установленным софтом, под который в данный момент я пишу код. По мере необходимости, в настройках проекта я указываю ту или иную машинку, на которой желаю в данный момент выполнять удалённую отладку.

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

    Итак, поехали...

    Под виртуальные машинки я зарезервировал отдельный жёсткий диск, создав на нём каталог vm. В этом каталоге разместил два подкаталога:
    • templates
    • work
    Каталог templates предназначен для хранения готовых, настроенных машинок-шаблонов, на основании которых следует создавать рабочие копии (т.е. те машинки, которые непосредственно используются при разработке и тестировании). Каталог work содержит непосредственно рабочие машинки.

    Каталог templates содержит подкаталоги:
    • base
    • develop
    • hosts
    В каталоге base созданы базовые виртуальные машинки, с установленными операционными системами, платформами .NET Framework и всеми обновлениями. Более из софта на них не установлено ничего. Итак созданы:
    • win7x64
    • win7x86
    • winXPx86
    Создав эти три машинки, я сделал их копии в подкаталоге hosts и на каждую последовательно установил AutoCAD 2009 - 2014 со всеми пакетами обновлений:
    • win7x64_acads
    • win7x86_acads
    • winXPx86_acads

      Примечание:
      Далее по тексту, словосочетания "на базе", "на основе" означают, что делается копия исходной машинки и все операции выполняются с этой копией. Если иное не оговорено отдельно, то создание копий выполняется средствами VirtualBox. При этом включена опция генерации для копии нового mac-адреса. Однако будут указаны случаи, когда следует копирование выполнять обычным способом (Ctrl + C, Ctrl +V) - эти случаи будут обозначены отдельно.

      Затем, на базе машинки win7x64, в подкаталоге develop был создан шаблон для разработки:
      • win7x64_develop
      На эту машинку последовательно установлены MS Visual Studio 2005 - 2013 со всеми пакетами обновлений. Никаких дополнительных программ, а так же пакетов SDK не ставилось.


      Все перечисленные выше машинки являются всего лишь базовыми заготовками, на основе которых можно создавать более детализированные, специализированные шаблоны виртуальных машинок. Теперь можно приступать к детализации...

      В каталоге work, на основе машинки win7x64_develop была создана более детализированная рабочая машинка, предназначенная для непосредственного использования (т.е. это уже не шаблон):
      • win7_dev
      Деталлизирована она в следующем:
      • Установлен антивирус Kaspersky.
      • Установлен архиватор 7zip.
      • Создан и подключен дополнительный виртуальный диск D.
      • На диске D созданы подкаталоги: sdk, src, test, public.
      • В подкаталог D:sdkAutodeskAutoCAD установил все ObjectARX SDK по подкаталогам AutoCAD 2009, ... 2014.
      • Последовательно установил все Wizards.
      • В каталоге D:src создал подкаталоги vs2005, ... vs2013 для отдельного хранения проектов каждой IDE.
      • В настройках каждой IDE прописал путь, указанный выше, для хранения её проектов.
      • Каталог public сделал доступным для чтениязаписи всем компьютерам в домашней группе.
      Каталог D:test предназначен для тестирования, при котором нет необходимости подключаться к удалённым машинкам. Хранение проектов и SDK на отдельном виртуальном диске позволяет отключать его, в случае необходимости и подключать к другому компьютеру.



      Далее, в каталоге work были созданы так же рабочие копии машинок win7x64_acads, win7x86_acads и winXPx86_acads:
      • win7x64_acads_(work)
      • win7x86_acads_(work)
      • winXPx86_acads_(work)
      На эти машинки дополнительно установлен антивирус Kaspersky. Копии этих виртуальных машин были сделаны не средствами VirtualBox, а обычным копированием (Ctrl + C, Ctrl + V) - иначе у AutoCAD может слететь лицензия (в частности это происходит с AutoCAD 2009). Вообще, после копирования машинок средствами VirtualBox приходится на полученной копии выполнять повторную активацию Windows, но на мой взгляд - это не проблема, а если для кого-то проблема, то Ctrl + C, Ctrl + V в помощь.

      На каждой из указанны выше рабочих машинок создан каталог public, которому разрешён доступ на чтениезапись всем компьютерам, подключенным к домашней сети.

      Примечание
      В настройках всех виртуальных машинок тип подключения был мною изменён с NAT на Сетевой мост, дабы создать свою виртуальную сеть.

      В результате, как это ни странно, мои виртуальные машинки работают ощутимо быстрее, чем мой физический компьютер на работе. Работать достаточно комфортно. Отладочная сборка при компиляции размещается в "расшаренном" (shared) каталоге удалённой машинки, на которой планируется выполнение отладки:

      Меняя в настройках проекта имя целевой удалённой машины мы, тем самым, быстро меняем условия нашего тестирования (версия ОС, её разрядность, целевое приложение, например AutoCAD или nanoCAD и т.п.):





      Если нужно, то указываем логин и пароль для удалённого подключения:


      А вот, собственно, и отладка:


      Маленькое пояснение: в заголовке отладочной машинки мы видим имя win7x64_acads(work), а в настройках проекта и в строке монитора удалённой отладки видим имя win7x64. Это различие объясняется просто: в каталоге work я создал дополнительную копию на базе шаблона win7x64_acads, воспользовавшись обычным Ctrl + C, Ctrl + V. Переименовывать машинку и диск не стал (поленился) - вместо этого подключив её в VirtualBox, переименовал ярлык на win7x64_acads(work). Т.о. в заголовке окна виртуальной машинки мы видим имя переименованного ярлыка. А настоящее имя тестовой машинки указано в настройках проекта и в мониторе удалённой отладки. Конечно, лучше было бы их переименовать в соответствии с ярлыком. Надеюсь, что в отличие от меня вы именно так и сделаете, не поленившись :).


      Подводим итоги
      На мой взгляд, совместное использование виртуальных машин и удалённой отладки существенно упрощают процесс разработки, тестирования и отладки кода.

      AutoCAD .NET API & IDisposable

      В данной заметке речь пойдёт об использовании IDisposable в AutoCAD .NET API. Если не освобождать ресурсы, то будет происходить утечка памяти, которая может привести к серьёзным проблемам. Дабы этого не происходило, нужно понимать, для каких объектов и когда следует вызывать Dispose(), а так же для каких объектов этого делать категорически нельзя.

      Для начала несколько ссылок, откуда черпалась информация:
        1. Cleaning up after yourself: how and when to dispose of AutoCAD objects in .NET
        2. Calling Dispose() on AutoCAD objects
        3. Examples of calling Dispose() on AutoCAD objects
        В AutoCAD класс DBObject реализовывает интерфейс IDisposable. Соответственно, указанный интерфейс реализуется и всеми классами, унаследованными от DBObject. Этот же интерфейс реализован и классами Document и Database, однако для Document  вызывать Dispose() никогда не следует (это делает сам AutoCAD), а так же нельзя вызывать Dispose() для объекта, хранящегося в свойстве Document.Database.

        "Вручную" вызывать Dispose() следует в следующих случаях:
        1. Для временных объектов, которые никогда не будут добавлены в базу данных чертежа. Например, это могут быть нерезидентные объекты, предоставленные через Autodesk.AutoCAD.Geometry.
        2. Для временных объектов, которые потенциально могут быть добавлены в базу данных чертежа, но тем не менее не были в неё добавлены.
        3. Если экземпляр класса Database был вами создан способом "new Database(...)" без открытия документа. При работе с созданным подобным образом объектом Database, необходимо не забывать предварительно инициализировать им свойство HostApplicationServices.WorkingDatabase, иначе можно получить исключение eWrongDatabase. По завершению работы с объектом, следует возвращать свойству HostApplicationServices.WorkingDatabase предыдущее значение.
        Внимание!
        Некоторые ресурсы, используемые экземплярами различных классов, могут быть разделяемыми, т.е. одним и тем же ресурсом пользуется сразу несколько разных объектов. К сожалению, в AutoCAD .NET API подобные ситуации могут привести к Fatal Error, если будет произведено освобождение такого общего ресурса в то время, как им продолжают пользоваться другие объекты. 

        Примечание 1
        Скорее всего, возникновение обозначенной выше проблемы с освобождением совместно используемых ресурсов обусловлено плохо написанным кодом разработчиков Autodesk: по-нормальному, для каждого совместно используемого ресурса должен вестись свой счётчик ссылок. В коде метода Dispose() значение счётчика должно сначала декрементироваться (т.е. уменьшаться на 1) и затем проверяться на равенство 0. Если равенство не выполняется, значит ресурс используется кем-то ещё - в этом случае общий ресурс не освобождается. Но если текущее значение счётчика равно 0, то ресурс можно смело освобождать. 

        Учитывая возможные проблемы при освобождении совместно используемых ресурсов, в подобных ситуациях компания Autodesk рекомендует пользоваться "методом научного тыка"  (см. вторую из обозначенных выше ссылок): если вдруг, добавив очередной вызов Dispose() вы обнаруживаете возникновение Fatal Error, то попробуйте закомментировать этот вызов. Если ошибка исчезла, значит в данном случае вызов Dispose() выполнять не следует (логично). Всех мест в API, где наблюдается проблема освобождения совместно используемых ресурсов, не знает даже Autodesk. Поэтому данные места вы порой будете определять экспериментально, методом проб и ошибок (к сожалению).

        Ресурсы всех объектов, полученных при помощи объекта Transaction, посредством метода GetObject(), а так же ресурсы новых объектов, добавленных в базу данных чертежа посредством метода AddNewlyCreatedDBObject(), будут освобождены автоматически в коде метода Dispose() этого объекта транзакции. Т.е. для них не нужно вызывать Dispose() персонально.

        Настоятельно не рекомендую инициализировать объект Transaction следующим образом:

           1:  // db - экземпляр Database
           2:  Transaction tr = db.TransactionManager.StartTransaction();
           3:  ...
           4:  tr.Commit();
           5:  tr.Dispose();

        При такой форме записи, если между инициализацией экземпляра транзакции и вызовом его метода Dispose() произойдёт необработанное исключение, то его Dispose() вызван не будет. Соответственно не будут освобождены ресурсы, связанные с этим объектом, а так же не будут вызваны методы Dispose() для всех объектов, полученных из базы данных при помощи этого экземпляра транзакции, а так же методы Dispose() новых объектов, с его помощью добавленных в базу данных чертежа.

        Дабы избежать обозначенной выше опасности, следует инициализацию объекта Transaction выполнять так:

           1:  // db - экземпляр Database
           2:  using(Transaction tr = db.TransactionManager.StartTransaction()){
           3:      ...
           4:      tr.Commit();
           5:  }

        При таком способе, Dispose() гарантированно будет вызван при выходе из блока using, как для объекта транзакции, так и для всех объектов, работающих в её контексте, даже если произойдёт необработанное исключение

        Примечание 2
        Использовать блок using следует и для инициализации объектов, созданных вне контекста транзакции, поскольку для них метод Dispose() вызывать необходимо вручную.