我正在研究为一种目前不被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存在,无论是什么
恳请协助
在做了更多的研究之后,所有这些问题的答案......是肯定的!
鉴于微软声明,所有的私有应用程序蜂巢加载自 RegLoadAppKey 必须使用打开蜂巢时获得的原始句柄,我非常好奇Visual Studio是如何实现这一点的,考虑到我在任何地方都看不到对这样一个句柄的引用,然而当我在WinDbg中步入注册表函数时,Process Monitor报告说私有蜂巢已被访问。
在踏入这些函数后,我发现其实Visual Studio会将所有的Win32注册表函数迂回到自己的特殊处理程序中,由它来决定是否需要重定向函数调用。
因此,我们可以得出这样的结论
这还是给我们留下了一个问题,那就是首先要构造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!