Reputation: 720
I am trying to write a VSIX for Visual Studio 2019 that controls multiple instances of the Visual Studio IDE. We are working on a networked project that requires some automation to perform testing of multiple users. In the past I would have used DTE in an external tool, but my understanding is that as of VS2017 the COM guids are no longer globally registered, so doing it within the IDE is the only way.
Regardless, I am trying to get the IVsDebugger so I can track events in the debugger. However, I am having no luck. I can get IVsDebugger2, 3, 4, 5 but not IVSDebugger. Here is the general flow of what I am doing:
void CaptureDebugger()
{
DTE dte = GetDTE(GetRemoteProcessID());
ServiceProvider sp = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte);
IVsDebugger vsDebugger = sp.GetService(typeof(SVsShellDebugger)) as IVsDebugger;
// vsDebugger is null!
IVsDebugger2 vsDebugger2 = sp.GetService(typeof(SVsShellDebugger)) as IVsDebugger2;
// vsDebugger2 is not null!
}
/// <summary>
/// Gets the DTE object from any devenv process.
/// </summary>
private static EnvDTE.DTE GetDTE(int processId)
{
object runningObject = null;
IBindCtx bindCtx = null;
IRunningObjectTable rot = null;
IEnumMoniker enumMonikers = null;
try
{
Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
bindCtx.GetRunningObjectTable(out rot);
rot.EnumRunning(out enumMonikers);
IMoniker[] moniker = new IMoniker[1];
IntPtr numberFetched = IntPtr.Zero;
while (enumMonikers.Next(1, moniker, numberFetched) == 0)
{
IMoniker runningObjectMoniker = moniker[0];
string name = null;
try
{
if (runningObjectMoniker != null)
{
runningObjectMoniker.GetDisplayName(bindCtx, null, out name);
}
}
catch (UnauthorizedAccessException)
{
// Do nothing, there is something in the ROT that we do not have access to.
}
Regex monikerRegex = new Regex(@"!VisualStudio.DTE\.\d+\.\d+\:" + processId, RegexOptions.IgnoreCase);
if (!string.IsNullOrEmpty(name) && monikerRegex.IsMatch(name))
{
Marshal.ThrowExceptionForHR(rot.GetObject(runningObjectMoniker, out runningObject));
}
}
}
finally
{
if (enumMonikers != null)
Marshal.ReleaseComObject(enumMonikers);
if (rot != null)
Marshal.ReleaseComObject(rot);
if (bindCtx != null)
Marshal.ReleaseComObject(bindCtx);
}
return runningObject as EnvDTE.DTE;
}
What confuses me is I get get the local IVsDebugger via the call
var MYDEBUGGER = Package.GetGlobalService(typeof(SVsShellDebugger)) as IVsDebugger;
Which I see is using a GlobalService. I don't think there is an equivalent in the DTE I retrieve.
Any insight?
Upvotes: 0
Views: 385
Reputation: 171
I ran into this issue as well (however in my case, I'm actually trying to retrieve the IVsDebugger
in proc rather than what sounds like out of proc); after debugging into how vsdebug!CDebugger::QueryInterface
works I determined the actual issue appears to be that the calling thread in your application needs to be STA.
vsdebug!CDebugger::QueryInterface
returns with HRESULT
0
0x80040155
(REGDB_E_IIDNOTREG
) by OLE due to CStdWrapper::GetPSFactory
failing to find a proxy DLL for this typeCRemoteUnknown::RemQueryInterface
to 0x80004002
(E_NOINTERFACE
)Marshal.QueryInterface
in C# to see what's going on directly.If your program contains in-proc components that live inside the remote Visual Studio process (as mine does) you can retrieve and execute your operations against the IVsDebugger
on the UI thread. Otherwise, you can potentially create a new Thread
and call thread.SetApartmentState(ApartmentState.STA)
on it prior to starting it
Upvotes: 3