从 GUID 创建任意 Visual Studio COM 对象(例如 IDebugEngine2) [英] Create Arbitrary Visual Studio COM Object (such as IDebugEngine2) from GUID

查看:17
本文介绍了从 GUID 创建任意 Visual Studio COM 对象(例如 IDebugEngine2)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究为 Visual Studio 当前不支持的语言开发新的项目系统.

I am investigating developing a new project system for a language not currently supported by Visual Studio.

这种语言已经存在第三方调试器适配器协议服务器,但是由于它似乎无法与 Visual Studio 中使用的本机 DAP 客户端正常工作,我想编写自己的 AD7Engine 并简单地推迟所有调用我不需要修改回 Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.dll 中的原始实现

There already exists a third party Debugger Adapter Protocol server for this language, however as it doesn't seem to work properly with the native DAP client used in Visual Studio I'd like to write my own AD7Engine and simply defer all the calls I don't need to modify back to the original implementation in Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.dll

理论上我觉得这个应该比较简单;给定目标 COM 对象

In theory I feel this should be relatively simple; given the target COM object

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

我想我应该能够做类似的事情

I think I should be able to do something like

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

但是这失败了,说这个类没有注册.即使我尝试使用这种技术创建我的 own AD7Engine 实例,它也会显示类未注册.我觉得这个问题的症结在于 COM 对象是在注册表中定义的 HKCU\Software\Microsoft\VisualStudio\\CLSID(或者可能在 Visual Studio 的私有注册表配置单元下)在较新的版本中),但是我强烈怀疑 Activator.CreateInstance 不知道该位置,因此无法查看如何创建它.

However this fails, saying the class is not registered. Even if I try and create an instance of my own AD7Engine using this technique it says class not registered. The crux of this issue, I feel, is that the COM object is defined in the registry under HKCU\Software\Microsoft\VisualStudio\<version>\CLSID (or maybe under Visual Studio's private registry hive in newer versions), however I strongly suspect Activator.CreateInstance does not know about that location so can't see how to create it.

我一直在 IDA 中分解 vsdebug.dll 以查看引擎创建细节的来源;最终,它使用与 thisthisthis

I've been pulling apart vsdebug.dll in IDA to see where the engine creation details come from; ultimately, it uses a very similar technique to this, this and 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 来自未知的地方,其他人只是计算或硬编码);我觉得应该有一些在 Visual Studio 上下文中创建任意 COM 对象的高级方法,类似于使用 ServiceProvider.GetService() 时.

however ultimately I feel I shouldn't have to resort to trying to calculate the VS CLSID registry path (which vsdebug.dll gets from somewhere unknown and everyone else just calculates or hardcodes); I feel there should be some high level way of creating arbitrary COM objects in the Visual Studio context, similar to when you use ServiceProvider.GetService().

就我而言,这整个事情是一个有趣的学习练习,所以这是否是一个好主意与我无关;我致力于在这个阶段找到答案;我只需要知道存在哪些 API

For my purposes, this whole thing is a fun learning exercise, so whether this is a good idea or not is irrelevant to me; I'm committed to finding the answer at this stage; I just have to know what APIs exist for either

  • 通过像 GetService 这样的简单 API 直接实现这一点
  • 创建在当前 Visual Studio 版本的 CLSID 注册表项下定义的对象的新实例,或
  • 在正在运行的扩展程序中访问 privateregistry.bin 下的密钥

非常感谢任何帮助

推荐答案

在做了更多的研究之后,所有这些问题的答案......都是肯定的!

After doing a bit more research, the answer to all of these questions...is yes!

鉴于 Microsoft 声明从 RegLoadAppKey 必须使用打开 hive 时获得的原始句柄,我很好奇 Visual Studio 是如何实现这一点的,因为我看不到任何对此类的引用任何地方的句柄,但是当我在 WinDbg 中跨过注册表函数时,进程监视器报告私有配置单元已被访问.

Given that Microsoft states that all of private application hives loaded from RegLoadAppKey must use the original handle obtained when opening the hive, I was very curious as to how Visual Studio was achieving this, considering I couldn't see any references to such a handle anywhere, yet when I stepped over registry functions in WinDbg, Process Monitor reported that the private hive had been accessed.

进入这些函数后,我发现实际上 Visual Studio 将所有 Win32 注册表函数绕道到其自己的特殊处理程序,该处理程序确定是否需要重定向函数调用.

After stepping into these functions I discovered that actually Visual Studio detours all of the Win32 registry functions to its own special handler that determines whether or not it needs to redirect the function call.

因此,我们可以得出结论

As such, we can conclude

  • 如果需要,任何访问 Visual Studio 注册表项的尝试都将自动重定向到 Visual Studio 的私有应用程序配置单元

这仍然给我们留下了必须首先构造根 Visual Studio 密钥的问题.事实证明,您可以使用 VSRegistry.RegistryRoot 方法来获取对各种配置存储的引用(尽管实际上只支持用户和配置类型)

This still leaves us with the problem of having to construct the root Visual Studio key in the first place. As it turns out, you can use the VSRegistry.RegistryRoot method to gain a reference to various configuration stores (although only user and configuration types are actually supported)

因此,我们可以通过

VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration);

这仍然给我们留下了实际构造目标 COM 对象的问题.碰巧的是,Visual Studio确实有一个用于此的 API:ILocalRegistry3

This still leaves us with the problem of actually constructing the target COM object. As it happens, Visual Studio does have an API for this: ILocalRegistry3!

如果您已经知道要创建的对象的 CLSID,则可以直接使用 CreateInstance 方法创建它.在我的情况下,从 Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.dll 反编译的 AD7Engine 上的 GuidAttribute 不知何故实际上与 CLSID 不匹配在注册表中的 AD7Metrics 键下定义.因此,我认为将引擎 ID GUID 转换为 AD7Engine COM 对象的 CLSID,然后创建实例更安全.

If you already know the CLSID of the object you wish to create, you can create it straight up with the CreateInstance method. In my case, somehow the GuidAttribute on the AD7Engine decompiled from Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.dll did not actually match the CLSID that was defined under the AD7Metrics key in the registry. As such, I consider it safer to translate the Engine ID GUID to the CLSID of the AD7Engine COM object, and then create the instance.

利用这种技术,还可以轻松创建在 Microsoft.VisualStudio.ProjectSystem.Debug.DebugEngines 类中指定的调试器的实例.

Utilizing this technique it also makes it easy to create instances of the debuggers specified in the Microsoft.VisualStudio.ProjectSystem.Debug.DebugEngines class.

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;
}

您可以使用以下帮助程序轻松枚举所有可用引擎

You can easily enumerate all available engines with the following helper

[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 或其他东西来创建.

I tested this on all 38 engines installed on my machine; most of them succeeded, potentially the ones that failed need to be created via their ProgramProvider or something.

耶!

这篇关于从 GUID 创建任意 Visual Studio COM 对象(例如 IDebugEngine2)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆