579b191d7c
Вынесены общие ComHelper (Release + Safe-геттеры) и GeomClassifiers (FaceType/EdgeType), устранены дубли в 5+ сервисах. Освобождение doc3d RCW в GetTopPart, void-возвраты вместо фиктивных bool, HashSet для OpKinds, DocumentTypeName в паспорте модели, FileExtension без аллокаций. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
163 lines
7.1 KiB
C#
163 lines
7.1 KiB
C#
using System.Runtime.InteropServices;
|
|
using System.Runtime.Versioning;
|
|
using Kompas.Mcp.Core.Interop;
|
|
using Kompas.Mcp.Core.Threading;
|
|
using Kompas6API5;
|
|
using KompasAPI7;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace Kompas.Mcp.Core;
|
|
|
|
/// <summary>
|
|
/// Подключение к КОМПАС и владение корневыми COM-объектами на время жизни сервера.
|
|
/// <para>
|
|
/// Подключается через API5 (<see cref="KompasObject"/>), так как он нужен для построения 3D
|
|
/// (ksPart) и снимков (ksDocument3D), а из него получает современный API7 (<see cref="IApplication"/>).
|
|
/// Все обращения к COM выполняются на STA-потоке <see cref="KompasDispatcher"/>.
|
|
/// </para>
|
|
/// </summary>
|
|
[SupportedOSPlatform("windows")]
|
|
public sealed class KompasSession : IDisposable
|
|
{
|
|
// Порядок попыток. Нужен KompasObject (API5), поэтому .7 не используем как точку входа.
|
|
// Проверено на v24 Home: зарегистрирован KOMPAS.Application.5 (не LT).
|
|
private static readonly string[] ProgIds =
|
|
{
|
|
"KOMPAS.Application.5",
|
|
"KOMPASLT.Application.5",
|
|
};
|
|
|
|
private readonly KompasDispatcher _dispatcher;
|
|
private readonly ILogger<KompasSession> _log;
|
|
|
|
private KompasObject? _kompas; // API5 root
|
|
private IApplication? _app; // API7 application
|
|
private bool _launchedByUs;
|
|
|
|
public KompasSession(KompasDispatcher dispatcher, ILogger<KompasSession>? log = null)
|
|
{
|
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
_log = log ?? NullLogger<KompasSession>.Instance;
|
|
}
|
|
|
|
public bool IsConnected => _app is not null;
|
|
|
|
/// <summary>Сведения о последнем успешном подключении (null, пока не подключались).</summary>
|
|
public KompasConnectionInfo? LastInfo { get; private set; }
|
|
|
|
/// <summary>Показать/скрыть окно КОМПАС.</summary>
|
|
public Task SetVisibleAsync(bool visible, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() => Application.Visible = visible, ct);
|
|
|
|
/// <summary>Корневой объект API5. Доступен только на STA-потоке после подключения.</summary>
|
|
public KompasObject Kompas =>
|
|
_kompas ?? throw new InvalidOperationException("Нет подключения к КОМПАС. Сначала вызовите ConnectAsync.");
|
|
|
|
/// <summary>Приложение API7. Доступно только на STA-потоке после подключения.</summary>
|
|
public IApplication Application =>
|
|
_app ?? throw new InvalidOperationException("Нет подключения к КОМПАС. Сначала вызовите ConnectAsync.");
|
|
|
|
/// <summary>Подключиться к КОМПАС: attach к запущенному или launch нового экземпляра.</summary>
|
|
public Task<KompasConnectionInfo> ConnectAsync(bool visible = true, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() => ConnectCore(visible), ct);
|
|
|
|
private KompasConnectionInfo ConnectCore(bool visible)
|
|
{
|
|
if (_app is not null)
|
|
return LastInfo ??= BuildInfo(_lastProgId);
|
|
|
|
// 1) attach к уже запущенному экземпляру
|
|
foreach (var id in ProgIds)
|
|
{
|
|
if (NativeMethods.GetActiveObject(id) is KompasObject ko)
|
|
{
|
|
_kompas = ko;
|
|
_lastProgId = id;
|
|
_launchedByUs = false;
|
|
_log.LogInformation("Присоединились к запущенному КОМПАС ({ProgId})", id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 2) запустить новый экземпляр
|
|
if (_kompas is null)
|
|
{
|
|
foreach (var id in ProgIds)
|
|
{
|
|
var type = Type.GetTypeFromProgID(id, throwOnError: false);
|
|
if (type is null) continue;
|
|
try
|
|
{
|
|
if (Activator.CreateInstance(type) is KompasObject ko)
|
|
{
|
|
_kompas = ko;
|
|
_lastProgId = id;
|
|
_launchedByUs = true;
|
|
_log.LogInformation("Запустили новый КОМПАС ({ProgId})", id);
|
|
break;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_log.LogWarning(ex, "Не удалось создать экземпляр {ProgId}", id);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_kompas is null)
|
|
throw new InvalidOperationException(
|
|
"Не удалось подключиться к КОМПАС. Убедитесь, что КОМПАС-3D установлен и запускается.");
|
|
|
|
_app = (IApplication?)_kompas.ksGetApplication7()
|
|
?? throw new InvalidOperationException("ksGetApplication7() вернул null — API7 недоступен.");
|
|
|
|
_app.Visible = visible;
|
|
|
|
return LastInfo = BuildInfo(_lastProgId);
|
|
}
|
|
|
|
private string _lastProgId = string.Empty;
|
|
|
|
private KompasConnectionInfo BuildInfo(string progId)
|
|
{
|
|
int major = 0, minor = 0, release = 0, build = 0;
|
|
try { _app!.GetSystemVersion(out major, out minor, out release, out build); }
|
|
catch (Exception ex) { _log.LogWarning(ex, "GetSystemVersion недоступен"); }
|
|
|
|
int buildNumber = 0;
|
|
try { buildNumber = _app!.BuildNumber; }
|
|
catch (Exception ex) { _log.LogWarning(ex, "BuildNumber недоступен"); }
|
|
|
|
return new KompasConnectionInfo
|
|
{
|
|
ProgId = progId,
|
|
WasLaunched = _launchedByUs,
|
|
Version = $"{major}.{minor}.{release}",
|
|
BuildNumber = buildNumber,
|
|
Edition = progId.StartsWith("KOMPASLT", StringComparison.OrdinalIgnoreCase) ? "Home/LT" : "Full",
|
|
};
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Освобождаем COM-объекты на том же STA-потоке, на котором они созданы.
|
|
try
|
|
{
|
|
// Ограниченное ожидание: не виснем на старте остановки, если STA-поток занят COM-вызовом.
|
|
_dispatcher.InvokeAsync(() =>
|
|
{
|
|
// Quit не вызываем: не «убиваем» КОМПАС пользователя (см. docs/OPEN_QUESTIONS.md).
|
|
if (_app is not null && Marshal.IsComObject(_app)) Marshal.ReleaseComObject(_app);
|
|
if (_kompas is not null && Marshal.IsComObject(_kompas)) Marshal.ReleaseComObject(_kompas);
|
|
_app = null;
|
|
_kompas = null;
|
|
}).Wait(TimeSpan.FromSeconds(5));
|
|
}
|
|
catch
|
|
{
|
|
// Диспетчер остановлен / вызов завис — COM освободит GC-финализатор.
|
|
}
|
|
}
|
|
}
|