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

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

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

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

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

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

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

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

  • Administrator
  • *****
  • Сообщений: 13197
  • Карма: 1701
  • Рыцарь 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
  • *****
  • Сообщений: 13197
  • Карма: 1701
  • Рыцарь 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? Без них же точно так-же можно вызвать?