Steven
Steven

Reputation: 720

How to retrieve IVsDebugger from external DTE for automation in Visual Studio 2019

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

Answers (1)

lordmilko
lordmilko

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.

  • When the calling thread in your application is MTA, while vsdebug!CDebugger::QueryInterface returns with HRESULT 0
  • This shortly gets turned into 0x80040155 (REGDB_E_IIDNOTREG) by OLE due to CStdWrapper::GetPSFactory failing to find a proxy DLL for this type
  • This error in turn gets converted by CRemoteUnknown::RemQueryInterface to 0x80004002 (E_NOINTERFACE)
  • Which is what is reported back to you if you try and 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

Related Questions