Automatically open Visual Studio Projects and Solutions using IMessageFilter

I wanted to learn more about what an IMessageFilter is and how it behaves. So I wanted to have some sample code that used it to demonstrate its behavior.

So I wrote a program that starts Visual Studio, calls some properties and methods (like Get Version and MainWindow.Activate)  and then opens a solution of one or more projects. It then iterates through all the projects of the solution, and for each project, iterates the project items and displays their names.

If you run the code with the MessageFilter.Register line removed, this is what shows in the status log:

10/27/16 18:26:36:838 Creating Thread

10/27/16 18:26:36:842 Starting VS

10/27/16 18:26:41:259 VS Version = 14.0

10/27/16 18:26:41:262 Calling to Activate Main window

10/27/16 18:26:44:340 Opening Solution C:\Users\calvinh\Documents\Visual Studio 2013\Projects\cppConsoleApplication1\cppConsoleApplication1.sln

10/27/16 18:26:44:370 task threw System.Runtime.InteropServices.COMException (0x80010001): Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))

   at EnvDTE._DTE.get_Solution()

   at WpfApplication1.MainWindow.<>c__DisplayClass5_0.<MainWindow_Loaded>b__2() in C:\Users\calvinh\AppData\Local\Temporary Projects\WpfApplication1\MainWindow.xaml.cs:line 134

 

The call to open a solution was rejected by Visual Studio’s implementation of IMessageFilter, returning RPC_E_CALL_REJECTED.

COM uses Remote Procedure calls to serve requests, and the server (in this case, VS) rejected the call at the time because it was too busy. You can tell by the time stamp how much time elapsed between requests.

A Rejected call can be handled by my own implementation of IMessageFilter. When I put that MessageFilter.Register line back in the code, I get a successful run:

 

10/27/16 18:31:19:831 Creating Thread

10/27/16 18:31:19:837 Starting VS

10/27/16 18:31:25:780 VS Version = 14.0

10/27/16 18:31:25:783 Calling to Activate Main window

10/27/16 18:31:29:022 Opening Solution C:\Users\calvinh\Documents\Visual Studio 2013\Projects\cppConsoleApplication1\cppConsoleApplication1.sln

10/27/16 18:31:29:038 RetryRejectedCall hTaskCallee 00004a04 dwTickCount 16 dwRejectType SERVERCALL_RETRYLATER

10/27/16 18:31:34:014 Solution opened C:\Users\calvinh\Documents\Visual Studio 2013\Projects\cppConsoleApplication1\cppConsoleApplication1.sln

10/27/16 18:31:34:149 Project cppConsoleApplication1

10/27/16 18:31:34:190 cppConsoleApplication1.vcxproj.filters

10/27/16 18:31:34:240 Source Files

10/27/16 18:31:34:276 cppConsoleApplication1.cpp

10/27/16 18:31:34:288 stdafx.cpp

10/27/16 18:31:34:299 Header Files

10/27/16 18:31:34:497 stdafx.h

10/27/16 18:31:34:521 targetver.h

 

 

<code>

 using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

// Start Visual Studio
// File ->New->Project->C#-> WPF appllication
// Project->References->COM-> Add a reference to Microsoft Developer Environment 10.0

namespace WpfApplication1
{
  public interface ILog
  {
    void AddStatusMsg(string message);
  }
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window, ILog
  {
    TextBox _txtStatus;
    ManualResetEventSlim _evDone;
    ManualResetEventSlim _evNext;
    public MainWindow()
    {
      InitializeComponent();
      this.WindowState = WindowState.Maximized;
      this.Loaded += MainWindow_Loaded;
      //var dte =(EnvDTE.DTE) Marshal.GetActiveObject("VisualStudio.DTE.14.0"); // get the instance of an already running instance of VS
      //var x = dte.Solution.FullName;

      this.Closing += (oc, ec) =>
       {
         if (_evDone != null && !_evDone.IsSet)
         {
           _evDone.Set();
         }
       };
    }

    public void AddStatusMsg(string msg)
    {
      Dispatcher.BeginInvoke(new Action(
          () =>
          {
            var txt = DateTime.Now.ToString("MM/dd/yy HH:mm:ss:fff ") + msg;
            _txtStatus.AppendText(txt + "\r\n");
            _txtStatus.ScrollToEnd();
          }
          ));
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
      try
      {

        _txtStatus = new TextBox()
        {
          Margin = new Thickness(10, 0, 0, 0),
          MaxLines = 60,
          IsReadOnly = true,
          VerticalScrollBarVisibility = ScrollBarVisibility.Auto
        };
        _evDone = new ManualResetEventSlim();
        _evNext = new ManualResetEventSlim();
        var btnQuit = new Button()
        {
          Content = "_Quit",
          HorizontalAlignment = HorizontalAlignment.Left,
          Width = 120
        };
        btnQuit.Click += (ob, eb) =>
        {
          AddStatusMsg("Button Quit clicked");
          _evDone.Set();
          this.Close();
        };
        var btnNext = new Button()
        {
          Content = "_Next",
          HorizontalAlignment = HorizontalAlignment.Left,
          Width = 120
        };
        btnNext.Click += (ob, eb) =>
        {
          _evNext.Set();
        };
        var sp = new StackPanel() { Orientation = Orientation.Vertical };
        sp.Children.Add(btnNext);
        sp.Children.Add(btnQuit);
        sp.Children.Add(_txtStatus);
        this.Content = sp;
        var slns = new List<string>();
        var docdir = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        var vsdirs = System.IO.Directory.EnumerateDirectories(docdir, "*visual studio*");
        foreach (var vsdir in vsdirs)
        {
          var projdir = System.IO.Path.Combine(vsdir, "Projects");
          slns.AddRange(System.IO.Directory.EnumerateFiles(projdir, "*.sln", System.IO.SearchOption.AllDirectories));
        }
        if (slns.Count < 1)
        {
          throw new InvalidOperationException("Couldn't find any solutions");
        }

        Action action = () =>
        {
          try
          {
                    // comment out this line to remove the Message Filter
                    MessageFilter.Register(this);

            AddStatusMsg("Starting VS");
            var tVS = System.Type.GetTypeFromProgID("VisualStudio.DTE.14.0");
            var ovs = Activator.CreateInstance(tVS);
            EnvDTE.DTE dte = (EnvDTE.DTE)ovs;

            AddStatusMsg($"VS Version = {dte.Version.ToString()}");
            AddStatusMsg("Calling to Activate Main window");
            dte.MainWindow.Activate();
            foreach (var sln in slns)
            {
              AddStatusMsg($"Opening Solution {sln}");
              dte.Solution.Open(sln);
              AddStatusMsg($"Solution opened {dte.Solution.FileName}");
              foreach (EnvDTE.Project project in dte.Solution.Projects)
              {
                AddStatusMsg($"Project {project.Name}");
                foreach (EnvDTE.ProjectItem projItem in project.ProjectItems)
                {
                  if (_evDone.IsSet) // user wants to quit out
                          {
                    return;
                  }
                  AddStatusMsg($"  {projItem.Name}");
                  var items = projItem.ProjectItems;
                  if (items != null)
                  {
                    foreach (EnvDTE.ProjectItem item in projItem.ProjectItems)
                    {
                      AddStatusMsg($"      {item.Name}");
                    }
                  }
                }
              }
              dte.Solution.Close();
              break;//do only one solution
                    }
            AddStatusMsg("Waiting for Done event");
            _evDone.Wait();
            AddStatusMsg("Got evDone.wait");
            dte.Quit();
          }
          catch (Exception ex)
          {
            AddStatusMsg($"task threw {ex.ToString()}");
          }
          MessageFilter.Revoke();
                  // once the thread is done, the msg filter and the COM objects created on that thread also go away
                };
        var invoke_on_main_thread_will_show_MessagePending = false;
        if (invoke_on_main_thread_will_show_MessagePending)
        {
          action.Invoke();
        }
        else
        {
          AddStatusMsg("Creating Thread");
          var thrd = new Thread((o) =>
          {
            action();
          });
          thrd.SetApartmentState(System.Threading.ApartmentState.STA);
          thrd.Start();
        }

        //var x = Task.Run(() =>
        //    {
        //    });
      }
      catch (Exception ex)
      {
        this.Content = ex.ToString();
      }
    }
    public class MessageFilter : IOleMessageFilter
    {
      //
      // Class containing the IOleMessageFilter
      // thread error-handling functions.

      ILog _logger;

      public MessageFilter(ILog logger)
      {
        this._logger = logger;
      }

      public static void Register(ILog logger)
      {
        IOleMessageFilter newFilter = new MessageFilter(logger);
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(newFilter, out oldFilter);
      }

      // Done with the filter, close it.
      public static void Revoke()
      {
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(null, out oldFilter);
      }

      //
      // IOleMessageFilter functions.
      // Handle incoming thread requests.
      int IOleMessageFilter.HandleInComingCall(int dwCallType,
        System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr
        lpInterfaceInfo)
      {
        _logger.AddStatusMsg($"HandleInComingCall dwCallType {dwCallType} hTaskCaller {hTaskCaller.ToInt32().ToString("x8")}  dwTickCount {dwTickCount}  lpInterfaceInfo {lpInterfaceInfo}");
        return (int)SERVERCALL.SERVERCALL_ISHANDLED;
      }

      // Thread call was rejected, so try again.
      int IOleMessageFilter.RetryRejectedCall(System.IntPtr
        hTaskCallee, int dwTickCount, int dwRejectType)
      {
        _logger.AddStatusMsg($"RetryRejectedCall  hTaskCallee {hTaskCallee.ToInt32().ToString("x8")}  dwTickCount {dwTickCount}  dwRejectType {(SERVERCALL)dwRejectType}");

        if (dwRejectType == (int)SERVERCALL.SERVERCALL_RETRYLATER)
        // flag = SERVERCALL_RETRYLATER.
        {
          // if we return > 100, COM will wait for this many milliseconds and then retry the call
          // Retry the thread call immediately if return >=0 & 
          // <100.
          return 200; // try 200 msecs later
        }
        // Too busy; cancel call.
        return -1; //The call should be canceled. COM then returns RPC_E_CALL_REJECTED from the original method call
      }

      int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee,
        int dwTickCount, int dwPendingType)
      {
        var pidCaller = 0;
        var processName = string.Empty;
        if (hTaskCallee != IntPtr.Zero)
        {
          var hthread = OpenThread(dwDesiredAccess: ThreadAccess.QUERY_INFORMATION, bInheritHandle: false, dwThreadId: hTaskCallee);
          if (hthread != IntPtr.Zero)
          {
            pidCaller = GetProcessIdOfThread(hthread);
            if (pidCaller != 0)
            {
              var proc = System.Diagnostics.Process.GetProcessById(pidCaller);
              if (proc != null)
              {
                processName = proc.ProcessName;
              }
            }
          }
        }
        _logger.AddStatusMsg($"MessagePending  hTaskCallee {hTaskCallee.ToInt32().ToString("x8")}  dwTickCount {dwTickCount}  dwPendingType {dwPendingType} {pidCaller} {processName} ");
        //Return the flag PENDINGMSG_WAITDEFPROCESS.
        return (int)PENDINGMSG.PENDINGMSG_WAITDEFPROCESS;
      }

      // Implement the IOleMessageFilter interface.
      [DllImport("Ole32.dll")]
      private static extern int
        CoRegisterMessageFilter(IOleMessageFilter newFilter, out
        IOleMessageFilter oldFilter);

      [DllImport("kernel32.dll", SetLastError = true)]
      static extern IntPtr OpenThread(
          ThreadAccess dwDesiredAccess,
          bool bInheritHandle,
          IntPtr dwThreadId
          );
      [Flags]
      public enum ThreadAccess : int
      {
        TERMINATE = (0x0001),
        SUSPEND_RESUME = (0x0002),
        GET_CONTEXT = (0x0008),
        SET_CONTEXT = (0x0010),
        SET_INFORMATION = (0x0020),
        QUERY_INFORMATION = (0x0040),
        SET_THREAD_TOKEN = (0x0080),
        IMPERSONATE = (0x0100),
        DIRECT_IMPERSONATION = (0x0200)
      }

      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern int GetProcessIdOfThread(IntPtr handle);

    }
    enum SERVERCALL
    {
      SERVERCALL_ISHANDLED = 0, //The object may be able to process the call
      SERVERCALL_REJECTED = 1, //The object cannot handle the call due to an unforeseen problem, such as network unavailability
      SERVERCALL_RETRYLATER = 2 //The object cannot handle the call at this time. For example, an application might return this value when it is in a user-controlled modal state.
    }
    enum PENDINGTYPE
    {
      PENDINGTYPE_TOPLEVEL = 1,
      PENDINGTYPE_NESTED = 2
    }
    enum PENDINGMSG
    {
      PENDINGMSG_CANCELCALL = 0, //Cancel the outgoing call
      PENDINGMSG_WAITNOPROCESS = 1, //Wait for the return and don't dispatch the message
      PENDINGMSG_WAITDEFPROCESS = 2 //Wait and dispatch the message
    }

    [ComImport(), Guid("00000016-0000-0000-C000-000000000046"),
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    interface IOleMessageFilter
    {
      [PreserveSig]
      int HandleInComingCall(
          int dwCallType,
          IntPtr hTaskCaller,
          int dwTickCount,
          IntPtr lpInterfaceInfo);

      [PreserveSig]
      int RetryRejectedCall(
          IntPtr hTaskCallee,
          int dwTickCount,
          int dwRejectType);

      [PreserveSig]
      int MessagePending(
          IntPtr hTaskCallee,
          int dwTickCount,
          int dwPendingType);
    }
  }

}

</code>