Работа с AutoCAD через COM из внешних приложений

Автор Тема: Работа с AutoCAD через COM из внешних приложений  (Прочитано 24558 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Выношу на обсуждение свой простенький пример "AutoCAD through COM" на предмет замечаний и предложений в плане улучшения кода. Пример кода постепенно будет редактироваться, добавляя различного рода полезные (для наглядности) операции. Возможно позднее полный код проекта закину на Bitbucket (зависит от активности участников темы), но пока для общего понимания достаточно и этого. Свои соображения и замечания так же буду опубликовывать в последующих сообщениях данной темы.

Исходные данные:
На одной вирт. машинке (Windows 8.1 x64 Rus) установлена Visual Studio 2015 Update 3.
На второй вирт. машинке (Windows 8.1 x64 Rus) установлены AutoCAD 2009-2017 Enu, а так же дополнительная русская локализация для AutoCAD 2013-2017.
Используется удалённая отладка.

Код - C# [Выбрать]
  1. /* C# 6.0 (Visual Studio 2015)
  2. Program.cs
  3. © Андрей Бушман, 2016
  4.  
  5. Пример работы с AutoCAD через COM из внешнего приложения. */
  6.  
  7. using System;
  8. using System.Reflection;
  9. using System.Runtime.InteropServices;
  10. using System.Threading;
  11. using static System.Console;
  12.  
  13. namespace Bushman.Sandbox.AutoCAD {
  14.     class Program {
  15.  
  16.         /// <summary>
  17.         /// Вывод в поток Error сообщения об ошибке.
  18.         /// </summary>
  19.         /// <param name="ex">Объект исключения, подлежащий
  20.         /// обработке.</param>
  21.         /// <param name="c">Цвет текста.</param>
  22.         static void WriteErrorMsg(Exception ex, ConsoleColor c
  23.             ) {
  24.  
  25.             ConsoleColor prew = ForegroundColor;
  26.             ForegroundColor = c;
  27.  
  28.             Error.WriteLine("\nException (HResult = {0}):\n{1}",
  29.                 ex.HResult, ex.Message);
  30.  
  31.             ForegroundColor = prew;
  32.         }
  33.  
  34.         private const int sleep = 500;
  35.  
  36.         /// <summary>
  37.         /// Метод предназначен для получения COM-объекта или
  38.         /// значения его свойств. Или же для вызова метода COM-
  39.         /// объекта, возвращающего некоторое значение.
  40.         /// </summary>
  41.         /// <param name="func">Лямбда-выражение, подлежащее
  42.         /// выполнению.</param>
  43.         /// <returns>Возвращаемый тип Object вызывающая сторона
  44.         /// должна присваивать dynamic-переменной, дабы можно
  45.         /// было полноценно использовать свойства, методы и
  46.         /// события полученного объекта (без рефлексии).
  47.         /// </returns>
  48.         static object CallFunc(Func<object> func) {
  49.             while (true) {
  50.                 try {
  51.                     object obj = func();
  52.                     return obj;
  53.                 }
  54.                 catch (COMException ex) {
  55.                     WriteErrorMsg(ex, ConsoleColor.Yellow);
  56.                     // В качестве эксперимента:
  57.                     // Thread.Sleep(sleep);
  58.                 }
  59.                 catch (Exception ex) {
  60.                     WriteErrorMsg(ex, ConsoleColor.Red);
  61.                     throw;
  62.                 }
  63.             }
  64.         }
  65.  
  66.         /// <summary>
  67.         /// Метод предназначен для изменения свойств COM-объекта
  68.         /// , или же для вызова метода COM-объекта, ничего не
  69.         /// возвращающего в качестве результата.
  70.         /// </summary>
  71.         /// <param name="func">Лямбда-выражение, подлежащее
  72.         /// выполнению.</param>
  73.         static void DoAction(Action func) {
  74.             while (true) {
  75.                 try {
  76.                     func();
  77.                     return;
  78.                 }
  79.                 catch (COMException ex) {
  80.                     WriteErrorMsg(ex, ConsoleColor.Yellow);
  81.                     // В качестве эксперимента:
  82.                     // Thread.Sleep(sleep);
  83.                 }
  84.                 catch (Exception ex) {
  85.                     WriteErrorMsg(ex, ConsoleColor.Red);
  86.                     throw;
  87.                 }
  88.             }
  89.         }
  90.  
  91.         static void Main(string[] args) {
  92.  
  93.             Title = "AutoCAD through COM";
  94.  
  95.             /* Вариант 1:
  96.                 Получить ссылку на экземпляр Application уже
  97.                 запущенного приложения AutoCAD.
  98.  
  99.                Примечание 1:
  100.                 Если параметром указывать "AutoCAD.Application",
  101.                 то создастся экземпляр Application той версии
  102.                 AutoCAD, которая была запущена последней.
  103.  
  104.                Примечание 2:
  105.                 Можно конкретизировать нужную версию, указывая
  106.                 Major: "AutoCAD.Application.19".
  107.             */
  108.  
  109.             /*
  110.             dynamic app = CallFunc(()=> Marshal.GetActiveObject(
  111.                "AutoCAD.Application"));
  112.             */
  113.  
  114.             /* *************************************************
  115.                 Вариант 2:
  116.                 Создать новый экземпляр Application приложения
  117.                 AutoCAD (см. выше примечания 1 и 2). */
  118.            
  119.             Type comAppType = Type.GetTypeFromProgID(
  120.                 "AutoCAD.Application");
  121.  
  122.             dynamic app = CallFunc(() => Activator
  123.                 .CreateInstance(comAppType));
  124.  
  125.             /* *************************************************
  126.                 Далее, для получения информации о методах,
  127.                 свойствах и событиях объекта Application (и не
  128.                 только его) смотрим документацию:
  129.                 http://help.autodesk.com/view/ACD/2017/ENU/?guid=GUID-A809CD71-4655-44E2-B674-1FE200B9FE30
  130.                
  131.                 Это приходится делать т.к. Intellisense не
  132.                 сможет давать подсказок для dynamic.
  133.             */
  134.  
  135.             /* Далее используем интересующие нас методы,
  136.                 свойства и события: */
  137.  
  138.             dynamic docs = CallFunc(() => app.Documents);
  139.  
  140.             // Открываем существующий документ
  141.             dynamic doc1 = CallFunc(() => docs.Open(
  142.                 @"c:\shared\111.dwg"));
  143.  
  144.             // Создаём новый документ на основе DWT-шаблона
  145.             dynamic doc2 = CallFunc(() => docs.Add(
  146.                 @"Tutorial-mArch.dwt"));
  147.  
  148.             dynamic count = CallFunc(() => docs.Count);
  149.             dynamic title = CallFunc(() => app.Caption);
  150.  
  151.             // по умолчанию Visible установлен в false
  152.             DoAction(() => app.Visible = true);
  153.  
  154.             WriteLine("\nPress any key for dociments close...");
  155.             ReadKey();
  156.  
  157.             // Сохранение документа
  158.             // DoAction(()=> doc1.Save());
  159.  
  160.             // закрытие без сохранения изменений
  161.             // DoAction(() => doc1.Close(false));
  162.  
  163.             // сохранение изменений и закрытие
  164.             DoAction(() => doc1.Close(true));
  165.  
  166.             /* В методе SaveAs вторым параметром указываем
  167.                 числовое значение перечисления AcSaveAsType,
  168.                 которое можно найти в файле
  169.                 "ObjectARX 20XX\inc-win32\acadi.h": */
  170.  
  171.             // 36 - это значение для варианта DWG2007
  172.             DoAction(() => doc2.SaveAs(@"c:\shared\222.dwg", 36)
  173.                 );
  174.             DoAction(() => doc2.Close());
  175.  
  176.             WriteLine("\nPress any key for exit...");
  177.             ReadKey();
  178.  
  179.             DoAction(() => app.Quit());
  180.         }
  181.     }
  182. }



Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Однозначно нужно вызывать Thread.Sleep(sleep); внутри цикла так как AutoCAD не справится с такой "нагрузкой".
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Однозначно нужно вызывать Thread.Sleep(sleep); внутри цикла так как AutoCAD не справится с такой "нагрузкой".
Ок, раскомментирую, хотя в 2017-м у меня справлялся.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Ну и неплохо бы анализировать какое исключение возникает. Если не одно из этих трёх, то нет смыла в цикле:
  • RPC_E_CALL_REJECTED
  • RPC_E_RETRY
  • RPC_E_SERVERCALL_RETRYLATER
Нужно тогда из него сразу выходить.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Мне очень не нравится, что каждое обращение к COM-объекту приходится заворачивать в созданные мною обёртки...

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Ну и неплохо бы анализировать какое исключение возникает
Хорошо, добавлю сейчас. Каковы числовые коды этих ошибок? Я в блоке catch (COMException ex) добавлю проверку кодов ошибок.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Каковы числовые коды этих ошибок?
Файл WinError.h
Код - C++ [Выбрать]
  1. #define RPC_E_CALL_REJECTED              _HRESULT_TYPEDEF_(0x80010001L)
  2. #define RPC_E_RETRY                      _HRESULT_TYPEDEF_(0x80010109L)
  3. #define RPC_E_SERVERCALL_RETRYLATER      _HRESULT_TYPEDEF_(0x8001010AL)
В скобках шестнадцатеричные коды.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Если мне не изменяет память, то возможен еще и такой вариант ошибки, которую следует учитывать (т.е. тоже в цикле):
Код - C++ [Выбрать]
  1. #define RPC_S_SERVER_TOO_BUSY            1723L
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
И еще я заметил, что ты нигде не проверяешь состояние IsQuiescent, которое я делаю здесь:
http://adn-cis.org/kak-ispolzuya-visual-c-zapustit-autocad-i-zastavit-ego-vyipolnyat-nekotoryie-dejstviya.html
Код - C++ [Выбрать]
  1. #define nRetry (100)
  2. #define mSleep (500)
  3. IAcadState* pState = NULL;
  4.  
  5. bool checkReady()
  6. {
  7.   if (pState == NULL) return false;
  8.   VARIANT_BOOL bReady = VARIANT_FALSE;
  9.   for (int i = 0; i < nRetry; i++)  {
  10.     if (pState->get_IsQuiescent(&bReady) == S_OK && bReady == VARIANT_TRUE) break;
  11.     Sleep(mSleep);
  12.   }
  13.   return (bReady == VARIANT_TRUE);
  14. }
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Так, по поводу кодов ошибок...
Я вижу, что для ошибки RPC_E_SERVERCALL_RETRYLATER её код равен -2147417846 (в десятичной системе) - вижу в отладчике. В то же время значение 0x8001010AL соответствует десятичному числу 2147549450... Т.е. макрос _HRESULT_TYPEDEF_ каким-то образом преобразовывает значение 0x8001010AL? Значения в заголовочном файле имеют тип long, а значения кодов ошибок в управляемом исключении - int.

И еще я заметил, что ты нигде не проверяешь состояние IsQuiescent, которое я делаю здесь:
Сейчас почитаю об этом.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Еще такая статья от Microsoft: https://msdn.microsoft.com/en-us/library/ms228772.aspx
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
И еще я заметил, что ты нигде не проверяешь состояние IsQuiescent, которое я делаю здесь:
Реализовал проверку в виде такого метода:
Код - C# [Выбрать]
  1. private const int nRetry = 100;
  2.  
  3. /// <summary>
  4. /// Проверка доступности COM объекта Application,
  5. /// представляющего экземпляр AutoCAD.
  6. /// </summary>
  7. /// <param name="app">COM объект Application.</param>
  8. /// <returns>Возвращается true в случае готовности
  9. /// объекта Application к работе. В противном случае
  10. /// возвращается false.</returns>
  11. static bool CheckReady(object app) {
  12.  
  13.     if (app == null)
  14.         return false;
  15.  
  16.     dynamic acad = app;
  17.  
  18.     bool isQuiescent = false;
  19.  
  20.     for (int i = 0; i < nRetry; i++) {
  21.         dynamic state = acad.GetAcadState();
  22.         isQuiescent = state.IsQuiescent;
  23.  
  24.         if (isQuiescent)
  25.             break;
  26.         else
  27.             Thread.Sleep(sleep);
  28.     }
  29.     return isQuiescent;
  30. }

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Мне очень не нравится, что каждое обращение к COM-объекту приходится заворачивать в созданные мною обёртки...
А почему просто не взять dynamic ???

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
А почему просто не взять dynamic ???
Я не понял тебя. Поясни.

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Скорее это я не понял - зачем вообще эти методы DoAction и CallFunc? Без них же точно так-же можно вызвать?

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Скорее это я не понял - зачем вообще эти методы DoAction и CallFunc? Без них же точно так-же можно вызвать?
Всё упирается в необходимость обработки исключений, задержку и повторный вызов операции.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Скорее это я не понял - зачем вообще эти методы DoAction и CallFunc? Без них же точно так-же можно вызвать?
Они обрабатывают рантайм-исключения COMException. А.Н.Ривилис указал 4 типа, которые нужно ловить в случае работы с акадом.

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Странно, я почему-то не сталкивался - если акад занят, то занят. В любом случае количество повторных итераций я бы ограничил. По обертке - выглядеть такой код будет конечно ужасно, в с# по моему так и не ввели сахар типа op_dynamic, (хотя я не уверен, может уже и есть) он бы тут здорово помог.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Странно, я почему-то не сталкивался - если акад занят, то занят.
Это тебе пока крупно везло.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Внёс существенные изменения в код. Получился такой вариант:
Код - C# [Выбрать]
  1. /* C# 6.0 (Visual Studio 2015)
  2. Program.cs
  3. © Андрей Бушман, 2016
  4.  
  5. Пример работы с AutoCAD через COM из внешнего приложения. */
  6.  
  7. using System;
  8. using System.Reflection;
  9. using System.Runtime.InteropServices;
  10. using System.Threading;
  11. using static System.Console;
  12.  
  13. namespace Bushman.Sandbox.AutoCAD {
  14.     class Program {
  15.  
  16.         private const int sleep = 500;
  17.  
  18.         // TODO: нужно подставить правильное значение
  19.         const int RPC_E_CALL_REJECTED = 0;
  20.         // TODO: нужно подставить правильное значение
  21.         const int RPC_E_RETRY = 1;
  22.         // TODO: нужно подставить правильное значение
  23.         const int RPC_S_SERVER_TOO_BUSY = 2;
  24.  
  25.         const int RPC_E_SERVERCALL_RETRYLATER = -2147417846;
  26.  
  27.         /// <summary>
  28.         /// Вывод в поток Error сообщения об ошибке.
  29.         /// </summary>
  30.         /// <param name="ex">Объект исключения, подлежащий
  31.         /// обработке.</param>
  32.         /// <param name="c">Цвет текста.</param>
  33.         static void WriteErrorMsg(Exception ex, ConsoleColor c
  34.             ) {
  35.  
  36.             ConsoleColor prew = ForegroundColor;
  37.             ForegroundColor = c;
  38.  
  39.             Error.WriteLine("\nException (HResult = {0}):\n{1}",
  40.                 ex.HResult, ex.Message);
  41.  
  42.             ForegroundColor = prew;
  43.         }
  44.  
  45.         private const int nRetry = 100;
  46.  
  47.         /// <summary>
  48.         /// Проверка доступности COM объекта Application,
  49.         /// представляющего экземпляр AutoCAD.
  50.         /// </summary>
  51.         /// <param name="app">COM объект Application.</param>
  52.         /// <returns>Возвращается true в случае готовности
  53.         /// объекта Application к работе. В противном случае
  54.         /// возвращается false.</returns>
  55.         static bool CheckReady(object app) {
  56.  
  57.             if (app == null)
  58.                 return false;
  59.  
  60.             dynamic acad = app;
  61.  
  62.             bool isQuiescent = false;
  63.  
  64.             for (int i = 0; i < nRetry; i++) {
  65.                 dynamic state = null;
  66.  
  67.                 while (true) {
  68.                     try {
  69.                         state = acad.GetAcadState();
  70.                         break;
  71.                     }
  72.                     catch (COMException ex) {
  73.                         switch (ex.ErrorCode) {
  74.                         case RPC_E_CALL_REJECTED:
  75.                         case RPC_E_RETRY:
  76.                         case RPC_E_SERVERCALL_RETRYLATER:
  77.                         case RPC_S_SERVER_TOO_BUSY: {
  78.                                 WriteErrorMsg(ex, ConsoleColor
  79.                                     .Yellow);
  80.                                 Thread.Sleep(sleep);
  81.                                 break;
  82.                             }
  83.                         default: {
  84.                                 WriteErrorMsg(ex, ConsoleColor
  85.                                     .Red);
  86.                                 throw;
  87.                             }
  88.                         }
  89.                     }
  90.                     catch (Exception ex) {
  91.                         WriteErrorMsg(ex, ConsoleColor.Red);
  92.                         throw;
  93.                     }
  94.                 }
  95.                 isQuiescent = state.IsQuiescent;
  96.  
  97.                 if (isQuiescent)
  98.                     break;
  99.                 else
  100.                     Thread.Sleep(sleep);
  101.             }
  102.             return isQuiescent;
  103.         }
  104.  
  105.         /// <summary>
  106.         /// Получить COM объект Application.
  107.         /// </summary>
  108.         /// <param name="func">Лямбда-выражение, возвращающее
  109.         /// искомый COM объект Application.</param>
  110.         /// <returns>Возвращается COM объект Application.
  111.         /// </returns>
  112.         static object GetApp(Func<object> func) {
  113.  
  114.             if (func == null) {
  115.                 throw new ArgumentNullException(nameof(func));
  116.             }
  117.  
  118.             object obj = null;
  119.  
  120.             while (true) {
  121.                 try {
  122.                     obj = func();
  123.                     break;
  124.                 }
  125.                 catch (COMException ex) {
  126.                     switch (ex.ErrorCode) {
  127.                     case RPC_E_CALL_REJECTED:
  128.                     case RPC_E_RETRY:
  129.                     case RPC_E_SERVERCALL_RETRYLATER:
  130.                     case RPC_S_SERVER_TOO_BUSY: {
  131.                             WriteErrorMsg(ex, ConsoleColor
  132.                                 .Yellow);
  133.                             Thread.Sleep(sleep);
  134.                             break;
  135.                         }
  136.                     default: {
  137.                             WriteErrorMsg(ex, ConsoleColor
  138.                                 .Red);
  139.                             throw;
  140.                         }
  141.                     }
  142.                 }
  143.                 catch (Exception ex) {
  144.                     WriteErrorMsg(ex, ConsoleColor.Red);
  145.                     throw;
  146.                 }
  147.             }
  148.             return obj;
  149.         }
  150.  
  151.         /// <summary>
  152.         /// Метод предназначен для получения COM-объекта или
  153.         /// значения его свойств. Или же для вызова метода COM-
  154.         /// объекта, возвращающего некоторое значение.
  155.         /// </summary>
  156.         /// <param name="app">COM объект Application.</param>
  157.         /// <param name="func">Лямбда-выражение, подлежащее
  158.         /// выполнению.</param>
  159.         /// <returns>Возвращаемый тип Object вызывающая сторона
  160.         /// должна присваивать dynamic-переменной, дабы можно
  161.         /// было полноценно использовать свойства, методы и
  162.         /// события полученного объекта (без рефлексии).
  163.         /// </returns>
  164.         static object CallFunc(object app, Func<object> func) {
  165.  
  166.             if (app == null)
  167.                 throw new ArgumentNullException(nameof(app));
  168.  
  169.             if (func == null)
  170.                 throw new ArgumentNullException(nameof(func));
  171.  
  172.             bool isReady = CheckReady(app);
  173.  
  174.             object obj = null;
  175.  
  176.             if (isReady) {
  177.                 obj = func();
  178.             }
  179.             return obj;
  180.         }
  181.  
  182.         /// <summary>
  183.         /// Метод предназначен для изменения свойств COM-объекта
  184.         /// , или же для вызова метода COM-объекта, ничего не
  185.         /// возвращающего в качестве результата.
  186.         /// </summary>
  187.         /// <param name="app">COM объект Application.</param>
  188.         /// <param name="func">Лямбда-выражение, подлежащее
  189.         /// выполнению.</param>
  190.         static void DoAction(object app, Action func) {
  191.  
  192.             if (app == null)
  193.                 throw new ArgumentNullException(nameof(app));
  194.  
  195.             if (func == null)
  196.                 throw new ArgumentNullException(nameof(func));
  197.  
  198.             bool isReady = CheckReady(app);
  199.  
  200.             if (isReady) {
  201.                 func();
  202.             }
  203.         }
  204.  
  205.         static void Main(string[] args) {
  206.  
  207.             Title = "AutoCAD through COM";
  208.  
  209.             /* Вариант 1:
  210.                 Получить ссылку на экземпляр Application уже
  211.                 запущенного приложения AutoCAD.
  212.  
  213.                Примечание 1:
  214.                 Если параметром указывать "AutoCAD.Application",
  215.                 то создастся экземпляр Application той версии
  216.                 AutoCAD, которая была запущена последней.
  217.  
  218.                Примечание 2:
  219.                 Можно конкретизировать нужную версию, указывая
  220.                 Major: "AutoCAD.Application.19".
  221.             */
  222.  
  223.             /*
  224.             dynamic app = GetApp(()=> Marshal.GetActiveObject(
  225.                "AutoCAD.Application"));
  226.             */
  227.  
  228.             /* *************************************************
  229.                 Вариант 2:
  230.                 Создать новый экземпляр Application приложения
  231.                 AutoCAD (см. выше примечания 1 и 2). */
  232.  
  233.             // Требую запустить AutoCAD 2012
  234.             Type comAppType = Type.GetTypeFromProgID(
  235.                 "AutoCAD.Application");
  236.  
  237.             dynamic app = GetApp(() => Activator.CreateInstance(
  238.                 comAppType));
  239.  
  240.             if (app == null) {
  241.                 WriteLine("Не удалось получить объект " +
  242.                     "Application." +
  243.                     "\n\nPress any key for exit...");
  244.                 ReadKey();
  245.                 return;
  246.             }
  247.  
  248.             /* *************************************************
  249.                 Далее, для получения информации о методах,
  250.                 свойствах и событиях объекта Application (и не
  251.                 только его) смотрим документацию:
  252.                 http://help.autodesk.com/view/ACD/2017/ENU/?guid=GUID-A809CD71-4655-44E2-B674-1FE200B9FE30
  253.                
  254.                 Это приходится делать т.к. Intellisense не
  255.                 сможет давать подсказок для dynamic.
  256.             */
  257.  
  258.             /* Далее используем интересующие нас методы,
  259.                 свойства и события: */
  260.  
  261.             dynamic docs = CallFunc((object)app,
  262.                 () => app.Documents);
  263.  
  264.             // Открываем существующий документ
  265.             dynamic doc1 = CallFunc((object)app,
  266.                 () => docs.Open(@"c:\shared\111.dwg"));
  267.  
  268.             // Создаём новый документ на основе DWT-шаблона
  269.             dynamic doc2 = CallFunc((object)app,
  270.                 () => docs.Add(@"Tutorial-mArch.dwt"));
  271.  
  272.             dynamic count = CallFunc((object)app,
  273.                 () => docs.Count);
  274.             dynamic title = CallFunc((object)app,
  275.                 () => app.Caption);
  276.  
  277.             // по умолчанию Visible установлен в false
  278.             DoAction((object)app, () => app.Visible = true);
  279.  
  280.             WriteLine("\nPress any key for dociments close...");
  281.             ReadKey();
  282.  
  283.             // Сохранение документа
  284.             // DoAction((object)app, ()=> doc1.Save());
  285.  
  286.             // закрытие без сохранения изменений
  287.             // DoAction((object)app, () => doc1.Close(false));
  288.  
  289.             // сохранение изменений и закрытие
  290.             DoAction((object)app, () => doc1.Close(true));
  291.  
  292.             /* В методе SaveAs вторым параметром указываем
  293.                 числовое значение перечисления AcSaveAsType,
  294.                 которое можно найти в файле
  295.                 "ObjectARX 20XX\inc-win32\acadi.h": */
  296.  
  297.             // 36 - это значение для варианта DWG2007
  298.             DoAction((object)app,
  299.                 () => doc2.SaveAs(@"c:\shared\222.dwg", 36));
  300.  
  301.             DoAction((object)app, () => doc2.Close());
  302.  
  303.             WriteLine("\nPress any key for exit...");
  304.             ReadKey();
  305.  
  306.             DoAction((object)app, () => app.Quit());
  307.         }
  308.     }
  309. }
В коде пометками TODO обозначил недостающие изменения - неизвестные мне пока коды ошибок (у меня они не возникали). Так же обратил внимание на то, что порой (т.е. не всегда) по завершении работы данного кода в работающих процессах по прежнему отображается AutoCAD, хотя визуально приложение было закрыто. В этом случае убиваю такой процесс вручную. Так же бывает вижу, что временами в процессах остаются некие COM Surrogate. Полагаю, что они так же создаются моим кодом, потому в случае их наличия прибиваю их так же вручную.

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Код весьма уродлив. Конструктивные комментарии по его улучшению приветствуются.
Странно, я почему-то не сталкивался - если акад занят, то занят.
Честно говоря, странно что не сталкивался. Если запускать обозначенный мною код без этих обёрток, то все те случаи, когда рантайм-исключение не происходило, можно пересчитать по пальцам...

В любом случае количество повторных итераций я бы ограничил.
Хм... Я пока склонен полагать, что бесконечный цикл в данном контексте всё же подходит больше, т.к. является своего рода гарантией того, что нужная операция в конце-концов будет выполнена. Например, в GetApp бесконечный цикл гарантирует, что если AutoCAD установлен на машине (т.е. obj будет не null) то, соответственно, цикл будет повторять попытки инициализации obj до тех пор, пока не инициализирует. Но если бы вместо бесконечного цикла использовался for с предопределённым счётчиком, то существует вероятность того, что объект так и не успеет инициализироваться за то время, пока счётчик достигнет максимума.

Если говорить о CheckReady, то и там мы исходим из предположения, что объект Application у нас уже имеется на руках. А поскольку нам нужно с ним работать, то нам нужно гарантированно дождаться того момента, когда Application будет готово отвечать на наши действия - т.е. и в этом случае бесконечный while будет более надёжен чем for.

В моём предыдущем коде комбинация for\while - является избыточным. В виду этого все for удалил. Текущий вариант кода такой:

Код - C# [Выбрать]
  1. /* C# 6.0 (Visual Studio 2015)
  2. Program.cs
  3. © Андрей Бушман, 2016
  4.  
  5. Пример работы с AutoCAD через COM из внешнего приложения. */
  6.  
  7. using System;
  8. using System.Reflection;
  9. using System.Runtime.InteropServices;
  10. using System.Threading;
  11. using static System.Console;
  12.  
  13. namespace Bushman.Sandbox.AutoCAD {
  14.     class Program {
  15.  
  16.         const int sleep = 500;
  17.  
  18.         // TODO: нужно подставить правильное значение
  19.         const int RPC_E_CALL_REJECTED = 0;
  20.         // TODO: нужно подставить правильное значение
  21.         const int RPC_E_RETRY = 1;
  22.         // TODO: нужно подставить правильное значение
  23.         const int RPC_S_SERVER_TOO_BUSY = 2;
  24.  
  25.         const int RPC_E_SERVERCALL_RETRYLATER = -2147417846;
  26.  
  27.         /// <summary>
  28.         /// Вывод в поток Error сообщения об ошибке.
  29.         /// </summary>
  30.         /// <param name="ex">Объект исключения, подлежащий
  31.         /// обработке.</param>
  32.         /// <param name="c">Цвет текста.</param>
  33.         static void WriteErrorMsg(Exception ex, ConsoleColor c
  34.             ) {
  35.  
  36.             ConsoleColor prew = ForegroundColor;
  37.             ForegroundColor = c;
  38.  
  39.             Error.WriteLine("\nException (HResult = {0}):\n{1}",
  40.                 ex.HResult, ex.Message);
  41.  
  42.             ForegroundColor = prew;
  43.         }
  44.  
  45.         /// <summary>
  46.         /// Проверка доступности COM объекта Application,
  47.         /// представляющего экземпляр AutoCAD.
  48.         /// </summary>
  49.         /// <param name="app">COM объект Application.</param>
  50.         /// <returns>Возвращается true в случае готовности
  51.         /// объекта Application к работе. В противном случае
  52.         /// возвращается false.</returns>
  53.         static bool CheckReady(object app) {
  54.  
  55.             if (app == null)
  56.                 return false;
  57.  
  58.             dynamic acad = app;
  59.             bool isQuiescent = false;
  60.  
  61.             while (true) {
  62.                 try {
  63.                     dynamic state = acad.GetAcadState();
  64.                     isQuiescent = state.IsQuiescent;
  65.                     break;
  66.                 }
  67.                 catch (COMException ex) {
  68.                     switch (ex.ErrorCode) {
  69.                     case RPC_E_CALL_REJECTED:
  70.                     case RPC_E_RETRY:
  71.                     case RPC_E_SERVERCALL_RETRYLATER:
  72.                     case RPC_S_SERVER_TOO_BUSY: {
  73.                             WriteErrorMsg(ex, ConsoleColor
  74.                                 .Yellow);
  75.                             Thread.Sleep(sleep);
  76.                             break;
  77.                         }
  78.                     default: {
  79.                             WriteErrorMsg(ex, ConsoleColor
  80.                                 .Red);
  81.                             throw;
  82.                         }
  83.                     }
  84.                 }
  85.                 catch (Exception ex) {
  86.                     WriteErrorMsg(ex, ConsoleColor.Red);
  87.                     throw;
  88.                 }
  89.             }
  90.             return isQuiescent;
  91.         }
  92.  
  93.         /// <summary>
  94.         /// Получить COM объект Application.
  95.         /// </summary>
  96.         /// <param name="func">Лямбда-выражение, возвращающее
  97.         /// искомый COM объект Application.</param>
  98.         /// <returns>Возвращается COM объект Application.
  99.         /// </returns>
  100.         static object GetApp(Func<object> func) {
  101.  
  102.             if (func == null) {
  103.                 throw new ArgumentNullException(nameof(func));
  104.             }
  105.  
  106.             while (true) {
  107.                 try {
  108.                     object obj = func();
  109.                     return obj;
  110.                 }
  111.                 catch (COMException ex) {
  112.  
  113.                     switch (ex.ErrorCode) {
  114.                     case RPC_E_CALL_REJECTED:
  115.                     case RPC_E_RETRY:
  116.                     case RPC_E_SERVERCALL_RETRYLATER:
  117.                     case RPC_S_SERVER_TOO_BUSY: {
  118.                             WriteErrorMsg(ex, ConsoleColor
  119.                                 .Yellow);
  120.                             Thread.Sleep(sleep);
  121.                             break;
  122.                         }
  123.                     default: {
  124.                             WriteErrorMsg(ex, ConsoleColor
  125.                                 .Red);
  126.                             throw;
  127.                         }
  128.                     }
  129.                 }
  130.                 catch (Exception ex) {
  131.                     WriteErrorMsg(ex, ConsoleColor.Red);
  132.                     throw;
  133.                 }
  134.             }
  135.         }
  136.  
  137.         /// <summary>
  138.         /// Метод предназначен для получения COM-объекта или
  139.         /// значения его свойств. Или же для вызова метода COM-
  140.         /// объекта, возвращающего некоторое значение.
  141.         /// </summary>
  142.         /// <param name="app">COM объект Application.</param>
  143.         /// <param name="func">Лямбда-выражение, подлежащее
  144.         /// выполнению.</param>
  145.         /// <returns>Возвращаемый тип Object вызывающая сторона
  146.         /// должна присваивать dynamic-переменной, дабы можно
  147.         /// было полноценно использовать свойства, методы и
  148.         /// события полученного объекта (без рефлексии).
  149.         /// </returns>
  150.         static object CallFunc(object app, Func<object> func) {
  151.  
  152.             if (app == null)
  153.                 throw new ArgumentNullException(nameof(app));
  154.  
  155.             if (func == null)
  156.                 throw new ArgumentNullException(nameof(func));
  157.  
  158.             bool isReady = CheckReady(app);
  159.  
  160.             object obj = null;
  161.  
  162.             if (isReady) {
  163.                 obj = func();
  164.             }
  165.             return obj;
  166.         }
  167.  
  168.         /// <summary>
  169.         /// Метод предназначен для изменения свойств COM-объекта
  170.         /// , или же для вызова метода COM-объекта, ничего не
  171.         /// возвращающего в качестве результата.
  172.         /// </summary>
  173.         /// <param name="app">COM объект Application.</param>
  174.         /// <param name="func">Лямбда-выражение, подлежащее
  175.         /// выполнению.</param>
  176.         static void DoAction(object app, Action func) {
  177.  
  178.             if (app == null)
  179.                 throw new ArgumentNullException(nameof(app));
  180.  
  181.             if (func == null)
  182.                 throw new ArgumentNullException(nameof(func));
  183.  
  184.             bool isReady = CheckReady(app);
  185.  
  186.             if (isReady) {
  187.                 func();
  188.             }
  189.         }
  190.  
  191.         static void Main(string[] args) {
  192.  
  193.             Title = "AutoCAD through COM";
  194.  
  195.             /* Вариант 1:
  196.                 Получить ссылку на экземпляр Application уже
  197.                 запущенного приложения AutoCAD.
  198.  
  199.                Примечание 1:
  200.                 Если параметром указывать "AutoCAD.Application",
  201.                 то создастся экземпляр Application той версии
  202.                 AutoCAD, которая была запущена последней.
  203.  
  204.                Примечание 2:
  205.                 Можно конкретизировать нужную версию, указывая
  206.                 Major: "AutoCAD.Application.19".
  207.             */
  208.  
  209.             /*
  210.             dynamic app = GetApp(()=> Marshal.GetActiveObject(
  211.                "AutoCAD.Application"));
  212.             */
  213.  
  214.             /* *************************************************
  215.                 Вариант 2:
  216.                 Создать новый экземпляр Application приложения
  217.                 AutoCAD (см. выше примечания 1 и 2). */
  218.  
  219.             // Требую запустить AutoCAD 2012
  220.             Type comAppType = Type.GetTypeFromProgID(
  221.                 "AutoCAD.Application");
  222.  
  223.             dynamic app = GetApp(() => Activator.CreateInstance(
  224.                 comAppType));
  225.  
  226.             if (app == null) {
  227.                 WriteLine("Не удалось получить объект " +
  228.                     "Application." +
  229.                     "\n\nPress any key for exit...");
  230.                 ReadKey();
  231.                 return;
  232.             }
  233.  
  234.             /* *************************************************
  235.                 Далее, для получения информации о методах,
  236.                 свойствах и событиях объекта Application (и не
  237.                 только его) смотрим документацию:
  238.                 http://help.autodesk.com/view/ACD/2017/ENU/?guid=GUID-A809CD71-4655-44E2-B674-1FE200B9FE30
  239.                
  240.                 Это приходится делать т.к. Intellisense не
  241.                 сможет давать подсказок для dynamic.
  242.             */
  243.  
  244.             /* Далее используем интересующие нас методы,
  245.                 свойства и события: */
  246.  
  247.             dynamic docs = CallFunc((object)app,
  248.                 () => app.Documents);
  249.  
  250.             // Открываем существующий документ
  251.             dynamic doc1 = CallFunc((object)app,
  252.                 () => docs.Open(@"c:\shared\111.dwg"));
  253.  
  254.             // Создаём новый документ на основе DWT-шаблона
  255.             dynamic doc2 = CallFunc((object)app,
  256.                 () => docs.Add(@"Tutorial-mArch.dwt"));
  257.  
  258.             dynamic count = CallFunc((object)app,
  259.                 () => docs.Count);
  260.             dynamic title = CallFunc((object)app,
  261.                 () => app.Caption);
  262.  
  263.             // по умолчанию Visible установлен в false
  264.             DoAction((object)app, () => app.Visible = true);
  265.  
  266.             WriteLine("\nPress any key for dociments close...");
  267.             ReadKey();
  268.  
  269.             // Сохранение документа
  270.             // DoAction((object)app, ()=> doc1.Save());
  271.  
  272.             // закрытие без сохранения изменений
  273.             // DoAction((object)app, () => doc1.Close(false));
  274.  
  275.             // сохранение изменений и закрытие
  276.             DoAction((object)app, () => doc1.Close(true));
  277.  
  278.             /* В методе SaveAs вторым параметром указываем
  279.                 числовое значение перечисления AcSaveAsType,
  280.                 которое можно найти в файле
  281.                 "ObjectARX 20XX\inc-win32\acadi.h": */
  282.  
  283.             // 36 - это значение для варианта DWG2007
  284.             DoAction((object)app,
  285.                 () => doc2.SaveAs(@"c:\shared\222.dwg", 36));
  286.  
  287.             DoAction((object)app, () => doc2.Close());
  288.  
  289.             WriteLine("\nPress any key for exit...");
  290.             ReadKey();
  291.  
  292.             DoAction((object)app, () => app.Quit());
  293.         }
  294.     }
  295. }

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Исправил баг в методах CallFunc и DoAction: добавил ожидание готовности приложения, а не просто разовую проверку, как это было прежде (под вечер с вниманием стало похуже...):

Код - C# [Выбрать]
  1. while (!CheckReady(app)) {
  2.     // ждём, когда приложение будет готово...
  3. }

Т.о. текущий вариант выглядит следующим образом:

Код - C# [Выбрать]
  1. /* C# 6.0 (Visual Studio 2015)
  2. Program.cs
  3. © Андрей Бушман, 2016
  4.  
  5. Пример работы с AutoCAD через COM из внешнего приложения. */
  6.  
  7. using System;
  8. using System.Reflection;
  9. using System.Runtime.InteropServices;
  10. using System.Threading;
  11. using static System.Console;
  12.  
  13. namespace Bushman.Sandbox.AutoCAD {
  14.     class Program {
  15.  
  16.         const int sleep = 500;
  17.  
  18.         // TODO: нужно подставить правильное значение
  19.         const int RPC_E_CALL_REJECTED = 0;
  20.         // TODO: нужно подставить правильное значение
  21.         const int RPC_E_RETRY = 1;
  22.         // TODO: нужно подставить правильное значение
  23.         const int RPC_S_SERVER_TOO_BUSY = 2;
  24.  
  25.         const int RPC_E_SERVERCALL_RETRYLATER = -2147417846;
  26.  
  27.         /// <summary>
  28.         /// Вывод в поток Error сообщения об ошибке.
  29.         /// </summary>
  30.         /// <param name="ex">Объект исключения, подлежащий
  31.         /// обработке.</param>
  32.         /// <param name="c">Цвет текста.</param>
  33.         static void WriteErrorMsg(Exception ex, ConsoleColor c
  34.             ) {
  35.  
  36.             ConsoleColor prew = ForegroundColor;
  37.             ForegroundColor = c;
  38.  
  39.             Error.WriteLine("\nException (HResult = {0}):\n{1}",
  40.                 ex.HResult, ex.Message);
  41.  
  42.             ForegroundColor = prew;
  43.         }
  44.  
  45.         /// <summary>
  46.         /// Проверка доступности COM объекта Application,
  47.         /// представляющего экземпляр AutoCAD.
  48.         /// </summary>
  49.         /// <param name="app">COM объект Application.</param>
  50.         /// <returns>Возвращается true в случае готовности
  51.         /// объекта Application к работе. В противном случае
  52.         /// возвращается false.</returns>
  53.         static bool CheckReady(object app) {
  54.  
  55.             if (app == null)
  56.                 return false;
  57.  
  58.             dynamic acad = app;
  59.             bool isQuiescent = false;
  60.  
  61.             while (true) {
  62.                 try {
  63.                     dynamic state = acad.GetAcadState();
  64.                     isQuiescent = state.IsQuiescent;
  65.                     break;
  66.                 }
  67.                 catch (COMException ex) {
  68.                     switch (ex.ErrorCode) {
  69.                     case RPC_E_CALL_REJECTED:
  70.                     case RPC_E_RETRY:
  71.                     case RPC_E_SERVERCALL_RETRYLATER:
  72.                     case RPC_S_SERVER_TOO_BUSY: {
  73.                             WriteErrorMsg(ex, ConsoleColor
  74.                                 .Yellow);
  75.                             Thread.Sleep(sleep);
  76.                             break;
  77.                         }
  78.                     default: {
  79.                             WriteErrorMsg(ex, ConsoleColor
  80.                                 .Red);
  81.                             throw;
  82.                         }
  83.                     }
  84.                 }
  85.                 catch (Exception ex) {
  86.                     WriteErrorMsg(ex, ConsoleColor.Red);
  87.                     throw;
  88.                 }
  89.             }
  90.             return isQuiescent;
  91.         }
  92.  
  93.         /// <summary>
  94.         /// Получить COM объект Application.
  95.         /// </summary>
  96.         /// <param name="func">Лямбда-выражение, возвращающее
  97.         /// искомый COM объект Application.</param>
  98.         /// <returns>Возвращается COM объект Application.
  99.         /// </returns>
  100.         static object GetApp(Func<object> func) {
  101.  
  102.             if (func == null) {
  103.                 throw new ArgumentNullException(nameof(func));
  104.             }
  105.  
  106.             while (true) {
  107.                 try {
  108.                     object obj = func();
  109.                     return obj;
  110.                 }
  111.                 catch (COMException ex) {
  112.  
  113.                     switch (ex.ErrorCode) {
  114.                     case RPC_E_CALL_REJECTED:
  115.                     case RPC_E_RETRY:
  116.                     case RPC_E_SERVERCALL_RETRYLATER:
  117.                     case RPC_S_SERVER_TOO_BUSY: {
  118.                             WriteErrorMsg(ex, ConsoleColor
  119.                                 .Yellow);
  120.                             Thread.Sleep(sleep);
  121.                             break;
  122.                         }
  123.                     default: {
  124.                             WriteErrorMsg(ex, ConsoleColor
  125.                                 .Red);
  126.                             throw;
  127.                         }
  128.                     }
  129.                 }
  130.                 catch (Exception ex) {
  131.                     WriteErrorMsg(ex, ConsoleColor.Red);
  132.                     throw;
  133.                 }
  134.             }
  135.         }
  136.  
  137.         /// <summary>
  138.         /// Метод предназначен для получения COM-объекта или
  139.         /// значения его свойств. Или же для вызова метода COM-
  140.         /// объекта, возвращающего некоторое значение.
  141.         /// </summary>
  142.         /// <param name="app">COM объект Application.</param>
  143.         /// <param name="func">Лямбда-выражение, подлежащее
  144.         /// выполнению.</param>
  145.         /// <returns>Возвращаемый тип Object вызывающая сторона
  146.         /// должна присваивать dynamic-переменной, дабы можно
  147.         /// было полноценно использовать свойства, методы и
  148.         /// события полученного объекта (без рефлексии).
  149.         /// </returns>
  150.         static object CallFunc(object app, Func<object> func) {
  151.  
  152.             if (app == null)
  153.                 throw new ArgumentNullException(nameof(app));
  154.  
  155.             if (func == null)
  156.                 throw new ArgumentNullException(nameof(func));
  157.  
  158.             while (!CheckReady(app)) {
  159.                 // ждём, когда приложение будет готово...
  160.             }
  161.  
  162.             object obj = func();
  163.             return obj;
  164.         }
  165.  
  166.         /// <summary>
  167.         /// Метод предназначен для изменения свойств COM-объекта
  168.         /// , или же для вызова метода COM-объекта, ничего не
  169.         /// возвращающего в качестве результата.
  170.         /// </summary>
  171.         /// <param name="app">COM объект Application.</param>
  172.         /// <param name="func">Лямбда-выражение, подлежащее
  173.         /// выполнению.</param>
  174.         static void DoAction(object app, Action func) {
  175.  
  176.             if (app == null)
  177.                 throw new ArgumentNullException(nameof(app));
  178.  
  179.             if (func == null)
  180.                 throw new ArgumentNullException(nameof(func));
  181.  
  182.             while (!CheckReady(app)) {
  183.                 // ждём, когда приложение будет готово...
  184.             }
  185.  
  186.             func();
  187.         }
  188.  
  189.         static void Main(string[] args) {
  190.  
  191.             Title = "AutoCAD through COM";
  192.  
  193.             /* Вариант 1:
  194.                 Получить ссылку на экземпляр Application уже
  195.                 запущенного приложения AutoCAD.
  196.  
  197.                Примечание 1:
  198.                 Если параметром указывать "AutoCAD.Application",
  199.                 то создастся экземпляр Application той версии
  200.                 AutoCAD, которая была запущена последней.
  201.  
  202.                Примечание 2:
  203.                 Можно конкретизировать нужную версию, указывая
  204.                 Major: "AutoCAD.Application.19".
  205.             */
  206.  
  207.             /*
  208.             dynamic app = GetApp(()=> Marshal.GetActiveObject(
  209.                "AutoCAD.Application"));
  210.             */
  211.  
  212.             /* *************************************************
  213.                 Вариант 2:
  214.                 Создать новый экземпляр Application приложения
  215.                 AutoCAD (см. выше примечания 1 и 2). */
  216.  
  217.             // Требую запустить AutoCAD 2012
  218.             Type comAppType = Type.GetTypeFromProgID(
  219.                 "AutoCAD.Application");
  220.  
  221.             dynamic app = GetApp(() => Activator.CreateInstance(
  222.                 comAppType));
  223.  
  224.             if (app == null) {
  225.                 WriteLine("Не удалось получить объект " +
  226.                     "Application." +
  227.                     "\n\nPress any key for exit...");
  228.                 ReadKey();
  229.                 return;
  230.             }
  231.  
  232.             /* *************************************************
  233.                 Далее, для получения информации о методах,
  234.                 свойствах и событиях объекта Application (и не
  235.                 только его) смотрим документацию:
  236.                 http://help.autodesk.com/view/ACD/2017/ENU/?guid=GUID-A809CD71-4655-44E2-B674-1FE200B9FE30
  237.                
  238.                 Это приходится делать т.к. Intellisense не
  239.                 сможет давать подсказок для dynamic.
  240.             */
  241.  
  242.             /* Далее используем интересующие нас методы,
  243.                 свойства и события: */
  244.  
  245.             dynamic docs = CallFunc((object)app,
  246.                 () => app.Documents);
  247.  
  248.             // Открываем существующий документ
  249.             dynamic doc1 = CallFunc((object)app,
  250.                 () => docs.Open(@"c:\shared\111.dwg"));
  251.  
  252.             // Создаём новый документ на основе DWT-шаблона
  253.             dynamic doc2 = CallFunc((object)app,
  254.                 () => docs.Add(@"Tutorial-mArch.dwt"));
  255.  
  256.             dynamic count = CallFunc((object)app,
  257.                 () => docs.Count);
  258.             dynamic title = CallFunc((object)app,
  259.                 () => app.Caption);
  260.  
  261.             // по умолчанию Visible установлен в false
  262.             DoAction((object)app, () => app.Visible = true);
  263.  
  264.             WriteLine("\nPress any key for dociments close...");
  265.             ReadKey();
  266.  
  267.             // Сохранение документа
  268.             // DoAction((object)app, ()=> doc1.Save());
  269.  
  270.             // закрытие без сохранения изменений
  271.             // DoAction((object)app, () => doc1.Close(false));
  272.  
  273.             // сохранение изменений и закрытие
  274.             DoAction((object)app, () => doc1.Close(true));
  275.  
  276.             /* В методе SaveAs вторым параметром указываем
  277.                 числовое значение перечисления AcSaveAsType,
  278.                 которое можно найти в файле
  279.                 "ObjectARX 20XX\inc-win32\acadi.h": */
  280.  
  281.             // 36 - это значение для варианта DWG2007
  282.             DoAction((object)app,
  283.                 () => doc2.SaveAs(@"c:\shared\222.dwg", 36));
  284.  
  285.             DoAction((object)app, () => doc2.Close());
  286.  
  287.             WriteLine("\nPress any key for exit...");
  288.             ReadKey();
  289.  
  290.             DoAction((object)app, () => app.Quit());
  291.         }
  292.     }
  293. }

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Кстати, обнаружил, что AutoCAD 2017 на кой-то чёрт создаёт кучу дополнительных процессов Chromium, которые завершаются вместе с AutoCAD:

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Честно говоря, странно что не сталкивался. Если запускать обозначенный мною код без этих обёрток, то все те случаи, когда рантайм-исключение не происходило, можно пересчитать по пальцам...
Действительно странно, сейчас посмотрел старые коды - да в основном там ранее связывание, но тем не менее есть и куски позднего - почему-то ошибок не попадалось (части эти ежедневно работают на различных машинах) - там естественно есть стандартная обработка ошибок - но она не "долбит" пока заработает - единственный вариант - что те куски всегда запущенны в строго определенный момент (не абы когда).

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
сейчас посмотрел старые коды - да в основном там ранее связывание, но тем не менее есть и куски позднего
Не зависит от типа связывания совсем. Это работа в связке клиент/сервер. Так что тут как повезет. Только если COM/ActiveX используется из dll-сборки внутри AutoCAD эти все проверки не нужны (не обязательны).
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Андрей Бушман
Небольшие исправления для тебя:
Код - C# [Выбрать]
  1. const uint RPC_E_CALL_REJECTED = 0x80010001;
  2. const uint RPC_E_RETRY = 0x80010109;
  3. const uint RPC_E_SERVERCALL_RETRYLATER = 0x8001010A;
  4. const uint RPC_S_SERVER_TOO_BUSY = 1723;
  5.  
  6.  
  7. catch (COMException ex) {
  8.        uint err = (uint) ex.ErrorCode;
  9.        switch (err) {
  10.        case RPC_E_CALL_REJECTED:
  11.        case RPC_E_RETRY:
  12.        case RPC_E_SERVERCALL_RETRYLATER:
  13.        case RPC_S_SERVER_TOO_BUSY: {
  14.                WriteErrorMsg(ex, ConsoleColor
  15.                    .Yellow);
  16.                Thread.Sleep(sleep);
  17.                break;
  18.            }
  19.        default: {
  20.                WriteErrorMsg(ex, ConsoleColor
  21.                    .Red);
  22.                throw;
  23.            }
  24.        }
  25. }
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Ну ежели так - то скомпилируйте под interop любой версии http://forum.dwg.ru/showthread.php?t=73266, там вызовы "пестрят" - анимация каждого поворота сделана через 10 "поворотиков" и при любом сбое из них "змея" должна была "сломаться" - то есть уже никогда не прийти в начальное состояния а стать "кривой", а там чтоб любую "фигирку" собрать вызовы на сотни идут.
з.ы. - забавно почти ровно 5 лет прошло между темами.
з.з.ы. - посмотрел код - там не сотни, там тысячи обращений за один сеанс - змею по каждому сегменту проворачивает и я "поломок" не встречал (так что-бы она не довернулась).

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Не зависит от типа связывания совсем. Это работа в связке клиент/сервер. Так что тут как повезет.
Правильно ли я понимаю, что такое поведение свойственно именно AutoCAD? Когда-то я средствами VBA из MS Access 2003-2007 программно создавал огромные навороченные документы в MS Excel 2003 (о чём писал тут). Там ведь, по сути тоже использовался COM, но у меня подобных проблем так же не возникало... Почему в том случае проблем не было, а в этом есть? Насколько я помню - использовал в VBA раннее связывание, т.е. выбирал галочки напротив библиотек MS Excel в редакторе кода VBA.

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
скомпилируйте под interop любой версии http://forum.dwg.ru/showthread.php?t=73266
Этой фразы я не понял. Скачал указанные DWG и архив. Архив распаковал и попробовал запустить ЗмейCAD2010.exe - получаю окошко с сообщением "Ошибка инициализации". Win 8.1 x64, AutoCAD 2009-2017 x64.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Правильно ли я понимаю, что такое поведение свойственно именно AutoCAD? Когда-то я средствами VBA из MS Access 2003-2007 программно создавал огромные навороченные документы в MS Excel 2003 (о чём писал тут). Там ведь, по сути тоже использовался COM, но у меня подобных проблем так же не возникало...
Погугли RPC_E_SERVERCALL_RETRYLATER - увидишь кучу сообщений и для Word и для Excel и для VS. Просто тебе везло.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
ЗмейCAD2010.exe - получаю окошко с сообщением "Ошибка инициализации"
dwg не нужен, exe'шник надо запустить при работающем acad2010. На момент написания interop сборки хранились в gac'е. Сейчас какой он там "схватит" и попытается запустить - не знаю. Прикладываю скомпилированную сборку под 2016 и сам проект (меняешь ссылки на "интеропы" любого акада - и будет работать под ним). В новых автокадах соответственно документ должен быть открыт.

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Дима... Оно конечно, может просто совпадение, но... После неудачных попыток запуска твоей змейки у меня слетели лицензии с AutoCAD 2010-2017. Осталась только на 2009-м...

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Дима... Оно конечно, может просто совпадение, но...
ЭЭЭЭ?? Сочувствую. В свое оправдание я могу сказать, что если бы мне поручили написать программу сносящие лицензии старше 2009-го, я бы сказал что не очень представляю как это сделать.

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Восстановил лицензии (удалось только для AutoCAD 2013-2017).
Прикладываю скомпилированную сборку под 2016
При первом запуске так же получил ошибку, обозначенную мною выше. После повторной попытке запуска открылось окошко "ЗмейCAD". После запуска "разворот по правому щелчку" змейка начала бесконечно вращаться. Окошко "ЗмейCAD" висит, не отвечает. В заголовке окна надпись: "Смотрим и ничего не трогае...". Прервал бесконечное вращение двойным кликом мышки по пространству модели. Теперь вроде работает как нужно: удаётся перемещаться по звеньям и вращать их.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
При первом запуске так же получил ошибку, обозначенную мною выше.
Подозреваю, что это одна из ошибок, связанных с RPC_E_XXXX - исключениями.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Андрей БушманАвтор темы

  • ADN Club
  • *****
  • Сообщений: 2000
  • Карма: 163
  • Пишу программки...
    • Блог
  • Skype: Compositum78
Небольшие исправления для тебя:
Спасибо, внёс их в свой код. На форуме пока не обновляю, т.к. желающие без проблем смогут внести указанные изменения самостоятельно.

Оффлайн Дима_

  • ADN Club
  • ****
  • Сообщений: 473
  • Карма: 66
Там логическая ошибка с "разворотом" которую я поленился обновлять - он не докручивал первый сегмент и если покрутили последний (это по сути вся змея) - то уходил в "вечную раскрутку" - лечиться в коде смещением на 1 "ось". Что было при первом запуске не знаю - допускаю и ошибку COM (не успело загрузиться что-то или Андрей забыл документ открыть - не видя нельзя сказать), но если ошибка (обращение к COM) встречалась бы так часто, что надо-бы было каждый вызов заворачивать - то змея бы точно не работала бы совсем (всегда была-бы ломанная - т.к. там на каждом повороте под сотню вызовов - сделано это специально - для плавности). В общем ИХМО к COM'у вполне можно обращаться с "обычным" уровнем проверок.
з.ы. У excel'я подтверждаю так-же при обращении бывают сбои, но мне пока всегда пока хватало опять-таки более скромных проверок (не на каждый вызов, а скажем так "блоками").
з.ы. Лицензии "вернулись". Я рад.

Оффлайн art_rrc

  • ADN Club
  • **
  • Сообщений: 70
  • Карма: 1
  • Skype: art_sapranovich
Приветствую всех. Возникла необходимость поработать с Аcadом через COM, но возник вопрос, который мне кажется подходит по заголовку темы и вместе с этим недостоен сознания новой (разве что в ветке про VBA). Поэтому попытаюсь спросить тут, надеюсь модераторы не заругают  :)
Не получается воспользоваться фильтрами выбора:
Недопустимый аргумент FilterType в Select
Пытался уже практически перебором решить, не помогло  :o , прошу помощи:
Код:
Извините, вам запрещён просмотр содержимого спойлеров.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Недопустимый аргумент FilterType в Select
Объяви его не как object, а как short [].
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13830
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн art_rrc

  • ADN Club
  • **
  • Сообщений: 70
  • Карма: 1
  • Skype: art_sapranovich
Огромное спасибо! Александр Наумович как всегда выше всяких похвал и по качеству совета и по оперативности.
Сделал так:
Код - C# [Выбрать]
  1. short[] FilterType = new short[1];
  2. FilterType[0]= 0;
  3. object[] FilterData = new object[1];
  4. FilterData[0] = "LINE";