Подключение к приложению Renga

Получение приложение из загруженного плагина

Для внутренних приложений, загружаемых в Renga в виде плагинов, вопрос получения COM-интерфейса, описывающего приложение Renga не стоит - просто вызывается соответствующий метод: На C#:

Renga.Application rengaApp = new Renga.Application();

На C++:

Renga::IApplicationPtr rengaApp = Renga::CreateApplication();

Получение приложения из внешнего процесса

Сложнее обстоит вопрос с доступом к приложению Renga из стороннего процесса. Так или иначе, способы будут завязаны на COM-методы, так как само API реализовано на COM-технологии. Есть 2 сценария -- создать новый процесс Renga и подключиться к текущему процессу.

C++

Для запуска нового процесса Renga создайте экземпляр COM-оболочки приложения, выполните с ней действия и закройте приложение. Листинг далее со странички официальной документации.

CoInitialize(nullptr);
auto renga = Renga::CreateApplication(CLSCTX_LOCAL_SERVER);
renga->PutVisible(VARIANT_TRUE);
renga->OpenProject(bstr_t(argv[1]));
// use Renga someway
renga->CloseProject(VARIANT_TRUE);
// Quit explicitly:
renga->Quit();

CoUninitialize();

Как правило, пользовательские приложения на C++ существуют только в виде плагинов, для них не рационально делать логику работы с COM из стороннего процесса. Автору во всяком случае неизвестны плагины к Renga, написанные на C++ и обращающиеся к ней извне (возможно, только крупные СОД). Тем более, что работа с COM в C++ мягко говоря плохая и очень сильно отдает "legacy" . Автор не использовал в своей практике подобных обращений, если вам надо -- вы можете попросить ИИ-агента помочь с этой задачей, автор не имеет желания пробовать это сам и писать здесь километровые листинги 😬.

C#

Marshal.GetActiveObject

Наиболее простая реализация на C# (фактически, на .NET) - обратиться к методу Marshal.GetActiveObject из пространства имён System.Runtime.InteropServices и передать ему в аргумент идентификатор Renga-приложения Renga.Application.1. Если приложение не запущено, то оно будет создано

Примечание: начиная с .NET 5+ эта функциональность удалена из .NET, вместо этого можете воспользоваться вызовами нативного API через DllImport

//From https://stackoverflow.com/a/65496277
public static class Marshal2
{
    internal const String OLEAUT32 = "oleaut32.dll";
    internal const String OLE32 = "ole32.dll";

    [System.Security.SecurityCritical]  // auto-generated_required
    public static Object GetActiveObject(String progID)
    {
        Object obj = null;
        Guid clsid;

        // Call CLSIDFromProgIDEx first then fall back on CLSIDFromProgID if
        // CLSIDFromProgIDEx doesn't exist.
        try
        {
            CLSIDFromProgIDEx(progID, out clsid);
        }
        //            catch
        catch (System.Exception)
        {
            CLSIDFromProgID(progID, out clsid);
        }

        GetActiveObject(ref clsid, IntPtr.Zero, out obj);
        return obj;
    }

    //[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
    [DllImport(OLE32, PreserveSig = false)]
    [System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.None)]
    [System.Security.SuppressUnmanagedCodeSecurity]
    [System.Security.SecurityCritical]  // auto-generated
    private static extern void CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

    //[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
    [DllImport(OLE32, PreserveSig = false)]
    [System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.None)]
    [System.Security.SuppressUnmanagedCodeSecurity]
    [System.Security.SecurityCritical]  // auto-generated
    private static extern void CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);

    //[DllImport(Microsoft.Win32.Win32Native.OLEAUT32, PreserveSig = false)]
    [DllImport(OLEAUT32, PreserveSig = false)]
    [System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.None)]
    [System.Security.SuppressUnmanagedCodeSecurity]
    [System.Security.SecurityCritical]  // auto-generated
    private static extern void GetActiveObject(ref Guid rclsid, IntPtr reserved, [MarshalAs(UnmanagedType.Interface)] out Object ppunk);


    [DllImport("ole32.dll")]
    internal static extern void GetRunningObjectTable(int reserved, out IRunningObjectTable prot);

    [DllImport("ole32.dll")]
    private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);

    public static List<string> GetAppMonikers(string appName)
    {
        IRunningObjectTable rot;
        GetRunningObjectTable(0, out rot);

        IEnumMoniker monikerEnumerator = null;
        rot.EnumRunning(out monikerEnumerator);
        if (monikerEnumerator == null)
            return null;

        monikerEnumerator.Reset();

        var registries = new List<IMoniker>();
        var registries2 = new List<string>();

        IntPtr pNumFetched = new IntPtr();
        IMoniker[] monikers = new IMoniker[1];
        while (monikerEnumerator.Next(1, monikers, pNumFetched) == 0)
        {
            IBindCtx bindCtx;
            CreateBindCtx(0, out bindCtx);
            if (bindCtx == null)
                continue;

            string displayName;
            monikers[0].GetDisplayName(bindCtx, null, out displayName);
            //registries2.Add(displayName);
            if (displayName.Contains(appName)) registries.Add(monikers[0]);
        }
        return registries2;
    }
}

После прекращения работы с приложением Renga извне, если оно более не нужно, закройте его и освободите COM-объект приложения (далее renga) с помощью стандартного метода:

System.Runtime.InteropServices.Marshal.ReleaseComObject(renga);

Running Object Table

Официальная справка предлагает также обратить внимание на Windows-специфичную технологию "Running Object Table". Переиначивая примеры со справки конструкция, позволяющая получить все запущенные в системе процессы Renga будет выглядеть так:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

public static Renga.IApplication[] GetRengaApps()
{
    IRunningObjectTable rot;
    GetRunningObjectTable(0, out rot);

    List<Renga.IApplication> tmpApps = new List<Renga.IApplication>();
    var rengaMonikers = GetRengaMonikers();
    foreach (var moniker in rengaMonikers)
    {
        object comObject;
        // Get first Renga moniker in list
        rot.GetObject(moniker, out comObject);

        Renga.IApplication rengaApp = comObject as Renga.IApplication;
        if (rengaApp != null) tmpApps.Add(rengaApp);
    }
    return tmpApps.ToArray();
}

[DllImport("ole32.dll")]
private static extern void GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
private static List<IMoniker> GetRengaMonikers()
{
    IRunningObjectTable rot;
    GetRunningObjectTable(0, out rot);

    IEnumMoniker monikerEnumerator = null;
    rot.EnumRunning(out monikerEnumerator);
    if (monikerEnumerator == null)
        return null;

    monikerEnumerator.Reset();

    var registries = new List<IMoniker>();

    IntPtr pNumFetched = new IntPtr();
    IMoniker[] monikers = new IMoniker[1];
    while (monikerEnumerator.Next(1, monikers, pNumFetched) == 0)
    {
        IBindCtx bindCtx;
        CreateBindCtx(0, out bindCtx);
        if (bindCtx == null)
            continue;

        string displayName;
        monikers[0].GetDisplayName(bindCtx, null, out displayName);
        if (displayName.Contains("!Renga"))
            registries.Add(monikers[0]);
    }
    return registries;
}

Pyhton

Необходимо установить вспомогательный пакет pywin32

import win32com.client  
rengaApp = win32com.client.GetActiveObject("Renga.Application.1")  
if rengaApp is not None:  
    print("Application received successfully!")

Powershell

Ниже пример, как создать новый процесс Renga, вывести в консоль номер версии программы и закрыть приложение.

# --- Вспомогательная функция для освобождения ресрусов от COM-объекта
function Release-ComObject([object]$obj) {
    if ($null -ne $obj -and $obj -is [System.__ComObject]) {
        [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($obj)
    }
}

# Создадим экземпляр приложения Renga
$rengaApp = $null
try {
    $rengaApp = New-Object -ComObject "Renga.Application.1" -ErrorAction Stop
} catch {
    Write-Warning "Не удалось создать COM-объект Renga.Application.1. Если Renga не зарегистрирована как COM-сервер, выполните из папки установки: RengaProfessional.exe /regserver"
    Read-Host "Нажмите Enter для выхода"
    exit
}

# Делаем приложение видимым
$rengaApp.Visible = $true

# Выполняем некоторые действия, например, получаем информацию о версии приложения
Write-Host $rengaApp.VersionS

try { $rengaApp.Quit() } catch {}
Release-ComObject $rengaApp