3

I'm trying to interact with Visual Studio's application instance in which the user is working i.e. the one in the foreground. I'm using GetActiveObject() to get the instance of VS. But, in case there are multiple instances of VS running, it always gives the first instance (the one which was opened first).

I tried using AccessibleObjectFromWindow() and using Spy++ I got the Window Class for VS as "HwndWrapper", but the "hr" value is getting negative.

Below is the code:

if (hwnd != null)
{
    EnvDTE80.DTE2 dte = null;
    int hwndChild = 0;
    EnumChildCallback cb = new EnumChildCallback(EnumVisualStudioChildProc);
    EnumChildWindows(hwnd.ToInt32(), cb, ref hwndChild);
    if (hwndChild != 0)
    {
        const uint OBJID_NATIVEOM = 0xFFFFFFF0;
        Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
        int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out IDispatch ptr);
        if (hr >= 0)
        {
            dte = (EnvDTE80.DTE2)ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
        }
        else
        {
            Console.WriteLine("hr count " + hr + "\n");
        }
    }
    else
    {
        Console.WriteLine("hwndChild count " + hwndChild + "\n");
        dte = (EnvDTE80.DTE2)Marshal.GetActiveObject("VisualStudio.DTE." + VisualStudio.GetInstances());
    }
}

public static bool EnumVisualStudioChildProc(int hWnd, ref int lParam)
{
    StringBuilder buf = new StringBuilder(128);
    GetClassName(hWnd, buf, 128);
    if (buf.ToString().Contains("HwndWrapper"))
    {
        lParam = hWnd;
        return false;
    }
    return true;
}

I tried similar approach for finding the foreground instance of Word (Class name: _Wwg) /Excel also, there its working, Is class name I am using to retrieve the window correct?

Aniket Bhansali
  • 630
  • 12
  • 33
  • 1
    Finding the "right" window back isn't going to help you getting the right EnvDTE interface reference. Notorious problem with out-of-process COM servers btw, the only way to distinguish multiple running instances is by what document(s) they have loaded. On the theory that you'd want to tinker with the document and not the specific instance. Code [is here](https://stackoverflow.com/a/33758446/17034). That gets you the main window from the [MainWindow property](https://learn.microsoft.com/en-us/dotnet/api/envdte._dte.mainwindow), compare Z-order with GetWindow(). – Hans Passant Mar 15 '19 at 09:40
  • Thanks a lot, it helped. What if I have to do the same thing for other applications too (for eg. Photoshop etc). Can you please suggest some way? – Abhishek Mehta Mar 18 '19 at 05:48

2 Answers2

2

One solution is to use UI Automation. So you need to add a reference to UIAutomationClient and UIAutomationTypes, and then use a code like the following sample:

// get the foreground window handle.
// here I used the Windows GetForegroundWindow function but you can use
// any function that defines what is the active/foreground window in your context
var foreground = GetForegroundWindow();

// get all Visual Studio main windows (from the desktop)
foreach (AutomationElement child in AutomationElement.RootElement.FindAll(
    TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, "VisualStudioMainWindow")))
{
    // note the unfortunate 32-bit that UI automation uses instead of IntPtr...
    // in practise that shouldn't be a problem
    if (child.Current.NativeWindowHandle == foreground.ToInt32())
    {
        // this is the foreground Visual Studio
        // get its DTE instance
        var obj = GetVisualStudioInstance(child.Current.ProcessId);        
    }
}

// see doc at https://learn.microsoft.com/en-us/previous-versions/ms228755(v=vs.140)
public static object GetVisualStudioInstance(int processId)
{
    CreateBindCtx(0, out var ctx);
    if (ctx == null)
        return null;

    ctx.GetRunningObjectTable(out var table);
    table.EnumRunning(out var enumerator);
    var monikers = new IMoniker[1];
    while (enumerator.Next(1, monikers, IntPtr.Zero) == 0)
    {
        monikers[0].GetDisplayName(ctx, null, out var name);
        if (Regex.Match(name, @"!VisualStudio.DTE\.[0-9]*\.[0-9]*:" + processId).Success)
        {
            table.GetObject(monikers[0], out var obj);
            return obj;
        }
    }
    return null;
}


[DllImport("user32")]
private static extern IntPtr GetForegroundWindow();

[DllImport("ole32")]
private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc); // from System.Runtime.InteropServices.ComTypes
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • I am able to get the foreground Visual Studio, even the process Id (which is different for each instance of VS). But I need the active "instance" of Visual Studio, or we can say active instance (object) of the process "devenv". – Abhishek Mehta Mar 15 '19 at 09:10
  • What do you mean by "active instance"? – Simon Mourier Mar 15 '19 at 09:31
  • And I think `GetForegroundWindow()` gives the foreground Visual Studio already when Visual Studio is in the foreground. So that `foreach` and `if` are kind of redundant here. – Abhishek Mehta Mar 15 '19 at 09:34
  • I hope [this](https://stackoverflow.com/questions/31361795/find-existing-instance-of-office-application) post can explain what I mean by "active instance". `Marshal.GetActiveObject()` gives you an instance of an application by mentioning the progID. – Abhishek Mehta Mar 15 '19 at 09:42
  • No, they are not redundant, try the code and you will see. I still don't understand what you mean by a supposedly one and only "active instance". You can get any Visual Studio instance using GetActiveObject once you know its process id (you can build a moniker using progid + process id yes), but that has nothing to do with the concept of "active instance". – Simon Mourier Mar 15 '19 at 10:31
  • Can you help me with how to get a specific instance of an application using its process id through `GetActiveObject()`? – Abhishek Mehta Mar 18 '19 at 06:29
  • Thank you, it worked. Can same technique be used for other applications also (like adobe apps)? – Abhishek Mehta Mar 18 '19 at 13:02
  • Nope. The ROT + moniker stuff is specific to Visual Studio. Only few apps register to the ROT. You can see if an app registers anything in the ROT using a tool such as this one RotView: http://alax.info/blog/1444 – Simon Mourier Mar 18 '19 at 15:18
  • Okay. Thanks again – Abhishek Mehta Mar 19 '19 at 05:07
-1

I guess it should work:

   var processName = "devenv";
   var active = Process.GetProcessesByName(searchName).OrderByDescending(x =>  x.Threads.OfType<ProcessThread>().Count(t => t.ThreadState != ThreadState.Wait)).FirstOrDefault();

the main point is filtering by thread State active status: t.ThreadState != ThreadState.Wait is just simplifying.

for current running application the solution is :

        IntPtr hwnd = GetForegroundWindow();
        uint pid;
        GetWindowThreadProcessId(hwnd, out pid);
        Process p = Process.GetProcessById((int)pid);
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId);

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

but it the second example determines only current running application process

Oleg Bondarenko
  • 1,694
  • 1
  • 16
  • 19
  • This gives me the Process which is anyway "devenv" for both the instance of Visual Studio. I actually want the active instance (object) of Visual Studio – Abhishek Mehta Mar 15 '19 at 08:53
  • yes ,background process might have active threads too but much lesser than active. Could you use adjusted example ? – Oleg Bondarenko Mar 15 '19 at 09:20
  • I hope [this](https://stackoverflow.com/questions/31361795/find-existing-instance-of-office-application) can explain what I want. But the problem with `Marshal.GetActiveObject()` is the parameter progID is same for all the instance of an application and when there are multiple instances of same application, it gives the first instance of the app i.e. the instance which was opened first. – Abhishek Mehta Mar 15 '19 at 09:53
  • I got your requirement. I have changed my first example for getting process with max count of active processes. It should be that you want. As the rule active version of duplicated applications has most not waiting threads. – Oleg Bondarenko Mar 15 '19 at 09:59
  • I'm sorry, what is s2 in s2.OrderByDescending()? – Abhishek Mehta Mar 15 '19 at 10:08
  • oh sorry, wrong copy paste . Now it should be better – Oleg Bondarenko Mar 15 '19 at 10:11