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

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

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

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

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

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

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

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

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

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

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

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

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