/* C# 6.0 (Visual Studio 2015)
Program.cs
© Андрей Бушман, 2016
Пример работы с AutoCAD через COM из внешнего приложения. */
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using static System.Console;
namespace Bushman.Sandbox.AutoCAD {
class Program {
private const int sleep = 500;
// TODO: нужно подставить правильное значение
const int RPC_E_CALL_REJECTED = 0;
// TODO: нужно подставить правильное значение
const int RPC_E_RETRY = 1;
// TODO: нужно подставить правильное значение
const int RPC_S_SERVER_TOO_BUSY = 2;
const int RPC_E_SERVERCALL_RETRYLATER = -2147417846;
/// <summary>
/// Вывод в поток Error сообщения об ошибке.
/// </summary>
/// <param name="ex">Объект исключения, подлежащий
/// обработке.</param>
/// <param name="c">Цвет текста.</param>
static void WriteErrorMsg(Exception ex, ConsoleColor c
) {
ConsoleColor prew = ForegroundColor;
ForegroundColor = c;
Error.WriteLine("\nException (HResult = {0}):\n{1}",
ex.HResult, ex.Message);
ForegroundColor = prew;
}
private const int nRetry = 100;
/// <summary>
/// Проверка доступности COM объекта Application,
/// представляющего экземпляр AutoCAD.
/// </summary>
/// <param name="app">COM объект Application.</param>
/// <returns>Возвращается true в случае готовности
/// объекта Application к работе. В противном случае
/// возвращается false.</returns>
static bool CheckReady(object app) {
if (app == null)
return false;
dynamic acad = app;
bool isQuiescent = false;
for (int i = 0; i < nRetry; i++) {
dynamic state = null;
while (true) {
try {
state = acad.GetAcadState();
break;
}
catch (COMException ex) {
switch (ex.ErrorCode) {
case RPC_E_CALL_REJECTED:
case RPC_E_RETRY:
case RPC_E_SERVERCALL_RETRYLATER:
case RPC_S_SERVER_TOO_BUSY: {
WriteErrorMsg(ex, ConsoleColor
.Yellow);
Thread.Sleep(sleep);
break;
}
default: {
WriteErrorMsg(ex, ConsoleColor
.Red);
throw;
}
}
}
catch (Exception ex) {
WriteErrorMsg(ex, ConsoleColor.Red);
throw;
}
}
isQuiescent = state.IsQuiescent;
if (isQuiescent)
break;
else
Thread.Sleep(sleep);
}
return isQuiescent;
}
/// <summary>
/// Получить COM объект Application.
/// </summary>
/// <param name="func">Лямбда-выражение, возвращающее
/// искомый COM объект Application.</param>
/// <returns>Возвращается COM объект Application.
/// </returns>
static object GetApp(Func<object> func) {
if (func == null) {
throw new ArgumentNullException(nameof(func));
}
object obj = null;
while (true) {
try {
obj = func();
break;
}
catch (COMException ex) {
switch (ex.ErrorCode) {
case RPC_E_CALL_REJECTED:
case RPC_E_RETRY:
case RPC_E_SERVERCALL_RETRYLATER:
case RPC_S_SERVER_TOO_BUSY: {
WriteErrorMsg(ex, ConsoleColor
.Yellow);
Thread.Sleep(sleep);
break;
}
default: {
WriteErrorMsg(ex, ConsoleColor
.Red);
throw;
}
}
}
catch (Exception ex) {
WriteErrorMsg(ex, ConsoleColor.Red);
throw;
}
}
return obj;
}
/// <summary>
/// Метод предназначен для получения COM-объекта или
/// значения его свойств. Или же для вызова метода COM-
/// объекта, возвращающего некоторое значение.
/// </summary>
/// <param name="app">COM объект Application.</param>
/// <param name="func">Лямбда-выражение, подлежащее
/// выполнению.</param>
/// <returns>Возвращаемый тип Object вызывающая сторона
/// должна присваивать dynamic-переменной, дабы можно
/// было полноценно использовать свойства, методы и
/// события полученного объекта (без рефлексии).
/// </returns>
static object CallFunc(object app, Func<object> func) {
if (app == null)
throw new ArgumentNullException(nameof(app));
if (func == null)
throw new ArgumentNullException(nameof(func));
bool isReady = CheckReady(app);
object obj = null;
if (isReady) {
obj = func();
}
return obj;
}
/// <summary>
/// Метод предназначен для изменения свойств COM-объекта
/// , или же для вызова метода COM-объекта, ничего не
/// возвращающего в качестве результата.
/// </summary>
/// <param name="app">COM объект Application.</param>
/// <param name="func">Лямбда-выражение, подлежащее
/// выполнению.</param>
static void DoAction(object app, Action func) {
if (app == null)
throw new ArgumentNullException(nameof(app));
if (func == null)
throw new ArgumentNullException(nameof(func));
bool isReady = CheckReady(app);
if (isReady) {
func();
}
}
static void Main(string[] args) {
Title = "AutoCAD through COM";
/* Вариант 1:
Получить ссылку на экземпляр Application уже
запущенного приложения AutoCAD.
Примечание 1:
Если параметром указывать "AutoCAD.Application",
то создастся экземпляр Application той версии
AutoCAD, которая была запущена последней.
Примечание 2:
Можно конкретизировать нужную версию, указывая
Major: "AutoCAD.Application.19".
*/
/*
dynamic app = GetApp(()=> Marshal.GetActiveObject(
"AutoCAD.Application"));
*/
/* *************************************************
Вариант 2:
Создать новый экземпляр Application приложения
AutoCAD (см. выше примечания 1 и 2). */
// Требую запустить AutoCAD 2012
Type comAppType = Type.GetTypeFromProgID(
"AutoCAD.Application");
dynamic app = GetApp(() => Activator.CreateInstance(
comAppType));
if (app == null) {
WriteLine("Не удалось получить объект " +
"Application." +
"\n\nPress any key for exit...");
ReadKey();
return;
}
/* *************************************************
Далее, для получения информации о методах,
свойствах и событиях объекта Application (и не
только его) смотрим документацию:
http://help.autodesk.com/view/ACD/2017/ENU/?guid=GUID-A809CD71-4655-44E2-B674-1FE200B9FE30
Это приходится делать т.к. Intellisense не
сможет давать подсказок для dynamic.
*/
/* Далее используем интересующие нас методы,
свойства и события: */
dynamic docs = CallFunc((object)app,
() => app.Documents);
// Открываем существующий документ
dynamic doc1 = CallFunc((object)app,
() => docs.Open(@"c:\shared\111.dwg"));
// Создаём новый документ на основе DWT-шаблона
dynamic doc2 = CallFunc((object)app,
() => docs.Add(@"Tutorial-mArch.dwt"));
dynamic count = CallFunc((object)app,
() => docs.Count);
dynamic title = CallFunc((object)app,
() => app.Caption);
// по умолчанию Visible установлен в false
DoAction((object)app, () => app.Visible = true);
WriteLine("\nPress any key for dociments close...");
ReadKey();
// Сохранение документа
// DoAction((object)app, ()=> doc1.Save());
// закрытие без сохранения изменений
// DoAction((object)app, () => doc1.Close(false));
// сохранение изменений и закрытие
DoAction((object)app, () => doc1.Close(true));
/* В методе SaveAs вторым параметром указываем
числовое значение перечисления AcSaveAsType,
которое можно найти в файле
"ObjectARX 20XX\inc-win32\acadi.h": */
// 36 - это значение для варианта DWG2007
DoAction((object)app,
() => doc2.SaveAs(@"c:\shared\222.dwg", 36));
DoAction((object)app, () => doc2.Close());
WriteLine("\nPress any key for exit...");
ReadKey();
DoAction((object)app, () => app.Quit());
}
}
}