从GUID创建任意的Visual Studio COM对象(如IDebugEngine2)。

问题描述 投票:0回答:1

我正在研究为一种目前不被Visual Studio支持的语言开发一个新的项目系统。

已经有一个第三方的调试器适配器协议服务器用于这种语言,然而,由于它似乎不能与Visual Studio中使用的本地DAP客户端正常工作,我想写我自己的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中查看引擎创建的细节来自哪里;最终,它使用了一种与 这个, 这个这个

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().

对于我的目的来说,这整个事情是一个有趣的学习练习,所以这是否是一个好主意与我无关;我致力于在这个阶段找到答案;我只需要知道有什么API存在,无论是什么

  • 通过一个简单的API,如GetService,就可以直接实现这一点。
  • 创建当前Visual Studio版本的CLSID注册表键下定义的对象的新实例,或
  • 在运行中的扩展中访问 privateregistry.bin 下的密钥。

恳请协助

c# visual-studio visual-studio-extensions
1个回答
1
投票

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

鉴于微软声明,所有的私有应用程序蜂巢加载自 RegLoadAppKey 必须使用打开蜂巢时获得的原始句柄,我非常好奇Visual Studio是如何实现这一点的,考虑到我在任何地方都看不到对这样一个句柄的引用,然而当我在WinDbg中步入注册表函数时,Process Monitor报告说私有蜂巢已被访问。

在踏入这些函数后,我发现其实Visual Studio会将所有的Win32注册表函数迂回到自己的特殊处理程序中,由它来决定是否需要重定向函数调用。

因此,我们可以得出这样的结论

  • 如果需要,任何访问Visual Studio注册表键的尝试都将自动重定向到Visual Studio的私有应用程序蜂巢。

这还是给我们留下了一个问题,那就是首先要构造Visual Studio的根键。事实证明,你可以使用 VSRegistry.RegistryRoot 获得各种配置存储的引用的方法(尽管实际上只支持用户和配置类型)。

因此,我们可以通过以下方式获得用户配置密钥的引用。

VSRegistry.RegistryRoot(__VsLocalRegistryType.RegType_Configuration);

这仍然给我们留下了实际构造目标COM对象的问题。恰好,Visual Studio 是否 有一个API。ILocalRegistry3!

如果你已经知道你想创建的对象的CLSID,你可以直接用 CreateInstance 方法。在我的案例中,不知为何 GuidAttribute 在...上 AD7Engine 反编译自 Microsoft.VisualStudio.Debugger.VSCodeDebuggerHost.dll 实际上并不符合在下面定义的CLSID。AD7Metrics 键在注册表中。因此,我认为比较安全的做法是将引擎ID GUID翻译成AD7Engine COM对象的CLSID,然后创建实例。

利用这种技术,也可以很容易地创建在 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 或什么的。

Yay!

© www.soinside.com 2019 - 2024. All rights reserved.