Создать произвольный COM-объект Visual Studio (например, IDebugEngine2) из ​​GUID - PullRequest
0 голосов
/ 26 мая 2020

Я занимаюсь разработкой новой системы проектов для языка, который в настоящее время не поддерживается Visual Studio.

Уже существует сторонний сервер протокола адаптера отладчика для этого языка, однако он, похоже, не работает правильно с собственным клиентом DAP, используемым в Visual Studio, я хотел бы написать свой собственный AD7Engine и просто отложить все вызовы, которые мне не нужно изменять, обратно к исходной реализации в Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.dll

Теоретически я считаю, что это должно быть относительно просто; учитывая целевой COM-объект

namespace Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.AD7.Implementation
{
  [ComVisible(true)]
  [Guid("8355452D-6D2F-41b0-89B8-BB2AA2529E94")]
  internal class AD7Engine

Я думаю, что смогу сделать что-то вроде

var type = Type.GetTypeFromCLSID(new Guid("8355452D-6D2F-41b0-89B8-BB2AA2529E94"));
var instance = Activator.CreateInstance(type);

Однако это не удается, поскольку класс не зарегистрирован. Даже если я попытаюсь создать экземпляр моего собственного AD7Engine, используя этот метод, он говорит, что класс не зарегистрирован. Суть этой проблемы, как мне кажется, в том, что COM-объект определен в реестре под HKCU\Software\Microsoft\VisualStudio\<version>\CLSID (или, возможно, под кустом частного реестра Visual Studio в более новых версиях), однако я сильно подозреваю, что Activator.CreateInstance не знает об этом месте. поэтому не вижу, как его создать.

Я разбирал vsdebug.dll в IDA, чтобы увидеть, откуда берутся детали создания движка; в конечном итоге он использует технику, очень похожую на this , this и this

v8 = GetLoader(a1, &rclsid);

if (v8 < 0)
    goto LABEL_26;
v8 = GetModulePath(a1, &bstrString);
if (v8 < 0)
{
    v4 = bstrString;
    goto LABEL_26;
}
v9 = CoCreateInstance(&rclsid, 0, dwClsContext, &_GUID_10e4254c_6d73_4c38_b011_e0049b2e0a0f, &ppv);
v4 = bstrString;
if (v9 < 0)
{
    v8 = -2147155456;
    goto LABEL_26;
}
v8 = (*(int(__stdcall * *)(LPVOID, BSTR, IID *, LPUNKNOWN, DWORD, LPVOID *))(*(_DWORD*)ppv + 28))(
    ppv,
    bstrString,
    a1,
    pUnkOuter,
    dwClsContext,
    v17);
if (v8 < 0)
{
    LABEL_26:
    v11 = CoCreateInstance(a1, pUnkOuter, dwClsContext, &IID_IUnknown, v17);
    if (v11 >= 0 || v8 != -2147155456)

, однако в конечном итоге я чувствую, что не должен прибегать к попытке вычислить путь реестра VS CLSID (который vsdebug.dll получает откуда-то неизвестно, а все остальные просто вычисляют или жестко кодируют); Я считаю, что должен быть какой-то высокоуровневый способ создания произвольных COM-объектов в контексте Visual Studio, аналогичный тому, когда вы используете ServiceProvider.GetService().

Для моих целей все это - забавное обучающее упражнение, поэтому стоит ли это хорошая идея или нет, меня это не касается; Я стремлюсь найти ответ на этом этапе; Мне просто нужно знать, какие API существуют для

  • прямого достижения этой цели с помощью простого API, такого как GetService
  • , создавая новые экземпляры объектов, определенных в разделе реестра CLSID текущей версии Visual Studio, или
  • доступ к ключам в privateregistry.bin в рамках работающего расширения

Любая помощь будет принята с благодарностью

1 Ответ

1 голос
/ 26 мая 2020

После проведения небольшого исследования ответ на все эти вопросы ... да!

Учитывая, что Microsoft заявляет, что все кусты частных приложений, загруженные из RegLoadAppKey , должны использовать исходный дескриптор, полученный при открытии улья, мне было очень любопытно, как Visual Studio добивалась этого, учитывая, что я нигде не видел ссылок на такой дескриптор, но когда я перешагнул через функции реестра в WinDbg, Process Monitor сообщил, что был осуществлен доступ к частному улью.

После перехода к этим функциям я обнаружил, что на самом деле Visual Studio обходит все функции реестра Win32 своему собственному специальному обработчику, который определяет, нужно ли перенаправлять вызов функции.

Таким образом, мы можем сделать вывод:

  • Любые попытки доступа к разделам реестра Visual Studio будут автоматически перенаправлены в куст частного приложения Visual Studio, если требуется

Это по-прежнему оставляет нам проблему о необходимости создания ключа root Visual Studio в первую очередь. Как оказалось, вы можете использовать метод VSRegistry.RegistryRoot, чтобы получить ссылку на различные хранилища конфигурации (хотя на самом деле поддерживаются только типы пользователей и конфигурации)

Итак, мы можем получить ссылку на конфигурацию пользователя key via

VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration);

Это по-прежнему оставляет нам проблему фактического создания целевого COM-объекта. Так получилось, что в Visual Studio есть API для этого: ILocalRegistry3 !

Если вы уже знаете CLSID объекта, который нужно sh создать, вы можете создать его прямо с помощью метода CreateInstance. В моем случае каким-то образом GuidAttribute на AD7Engine, декомпилированном из Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.dll, на самом деле не соответствовал CLSID, который был определен в разделе AD7Metrics в реестре. Таким образом, я считаю более безопасным преобразовать GUID идентификатора ядра в CLSID COM-объекта AD7Engine, а затем создать экземпляр.

Используя этот метод, он также упрощает создание экземпляров отладчиков, указанных в класс Microsoft.VisualStudio.ProjectSystem.Debug.DebugEngines.

private IDebugEngine2 VsCreateDebugEngine(Guid engineId)
{
    var config = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration);

    var subKeyPath = $"AD7Metrics\\Engine\\{{{engineId}}}";

    var engineKey = config.OpenSubKey(subKeyPath);

    if (engineKey == null)
        throw new ArgumentException($"Could not find an AD7Engine for GUID '{engineId}'");

    var clsid = engineKey.GetValue("CLSID")?.ToString();

    if (clsid == null)
        throw new InvalidOperationException($"GUID '{engineId}' does not have a CLSID value");

    var obj = VsCreateComObject(new Guid(clsid));

    return (IDebugEngine2) obj;
}

private object VsCreateComObject(Guid guid)
{
    var localRegistry = (ILocalRegistry3)Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(SLocalRegistry));

    Guid riid = VSConstants.IID_IUnknown;

    IntPtr ptr;

    var result = localRegistry.CreateInstance(
        guid,
        null,
        ref riid,
        (uint) Microsoft.VisualStudio.OLE.Interop.CLSCTX.CLSCTX_INPROC_SERVER,
        out ptr
    );

    if (result != VSConstants.S_OK)
        throw new ArgumentException($"Failed to retrieve GUID '{guid}', GUID may not exist");

    var obj = Marshal.GetObjectForIUnknown(ptr);

    return obj;
}

Вы можете легко перечислить все доступные движки с помощью следующего помощника

[DebuggerDisplay("Name = {Name}, EngineID = {EngineID}, {CLSID} = {CLSID}")]
class AD7Info
{
    public string Name { get; set; }

    public Guid EngineID { get; set; }

    public Guid? CLSID { get; set; }
}

private List<AD7Info> GetAD7Infos()
{
    var config = VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration);

    var engineKey = config.OpenSubKey("AD7Metrics\\Engine");

    return engineKey.GetSubKeyNames().Select(n =>
    {
        var entryKey = engineKey.OpenSubKey(n);

        var clsid = entryKey.GetValue("CLSID")?.ToString();

        return new AD7Info
        {
            Name = entryKey.GetValue("Name")?.ToString(),
            EngineID = new Guid(n),
            CLSID = clsid != null ? new Guid(clsid) : (Guid?) null
        };
    }).ToList();
}

Пример:

//Retrieve the "managed only" debugging engine
var result = VsCreateDebugEngine(DebuggerEngines.ManagedOnlyEngine);

или в моем случае

//Retrieve the VSCodeDebuggerHost debug engine
var result = VsCreateDebugEngine(new Guid("2833D225-C477-4388-9353-544D168F6030"));

Я тестировал это на всех 38 двигателях, установленных на моей машине; большинству из них это удалось, возможно, те, которые потерпели неудачу, должны быть созданы через их ProgramProvider или что-то в этом роде.

Ура!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...