Quantcast
Channel: Randy Riness @ SPSCC aggregator
Viewing all articles
Browse latest Browse all 3015

MSDN Blogs: Automatically open Visual Studio Projects and Solutions using IMessageFilter

$
0
0

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:UserscalvinhDocumentsVisual Studio 2013ProjectscppConsoleApplication1cppConsoleApplication1.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:UserscalvinhAppDataLocalTemporary ProjectsWpfApplication1MainWindow.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:UserscalvinhDocumentsVisual Studio 2013ProjectscppConsoleApplication1cppConsoleApplication1.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:UserscalvinhDocumentsVisual Studio 2013ProjectscppConsoleApplication1cppConsoleApplication1.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.0namespace WpfApplication1
{
  publicinterfaceILog
  {
    void AddStatusMsg(string message);
  }
  ///<summary>/// Interaction logic for MainWindow.xaml///</summary>publicpartialclassMainWindow : 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();
         }
       };
    }

    publicvoid AddStatusMsg(string msg)
    {
      Dispatcher.BeginInvoke(newAction(
          () =>
          {
            var txt = DateTime.Now.ToString("MM/dd/yy HH:mm:ss:fff ") + msg;
            _txtStatus.AppendText(txt + "rn");
            _txtStatus.ScrollToEnd();
          }
          ));
    }

    privatevoid MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
      try
      {

        _txtStatus = newTextBox()
        {
          Margin = newThickness(10, 0, 0, 0),
          MaxLines = 60,
          IsReadOnly = true,
          VerticalScrollBarVisibility = ScrollBarVisibility.Auto
        };
        _evDone = newManualResetEventSlim();
        _evNext = newManualResetEventSlim();
        var btnQuit = newButton()
        {
          Content = "_Quit",
          HorizontalAlignment = HorizontalAlignment.Left,
          Width = 120
        };
        btnQuit.Click += (ob, eb) =>
        {
          AddStatusMsg("Button Quit clicked");
          _evDone.Set();
          this.Close();
        };
        var btnNext = newButton()
        {
          Content = "_Next",
          HorizontalAlignment = HorizontalAlignment.Left,
          Width = 120
        };
        btnNext.Click += (ob, eb) =>
        {
          _evNext.Set();
        };
        var sp = newStackPanel() { Orientation = Orientation.Vertical };
        sp.Children.Add(btnNext);
        sp.Children.Add(btnQuit);
        sp.Children.Add(_txtStatus);
        this.Content = sp;
        var slns = newList<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)
        {
          thrownewInvalidOperationException("Couldn't find any solutions");
        }

        Action action = () =>
        {
          try
          {
                    // comment out this line to remove the Message FilterMessageFilter.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 = newThread((o) =>
          {
            action();
          });
          thrd.SetApartmentState(System.Threading.ApartmentState.STA);
          thrd.Start();
        }

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

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

      publicstaticvoid Register(ILog logger)
      {
        IOleMessageFilter newFilter = newMessageFilter(logger);
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(newFilter, out oldFilter);
      }

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

      //// IOleMessageFilter functions.// Handle incoming thread requests.intIOleMessageFilter.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.intIOleMessageFilter.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
      }

      intIOleMessageFilter.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")]
      privatestaticexternint
        CoRegisterMessageFilter(IOleMessageFilter newFilter, outIOleMessageFilter oldFilter);

      [DllImport("kernel32.dll", SetLastError = true)]
      staticexternIntPtr OpenThread(
          ThreadAccess dwDesiredAccess,
          bool bInheritHandle,
          IntPtr dwThreadId
          );
      [Flags]
      publicenumThreadAccess : 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)]
      publicstaticexternint GetProcessIdOfThread(IntPtr handle);

    }
    enumSERVERCALL
    {
      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.
    }
    enumPENDINGTYPE
    {
      PENDINGTYPE_TOPLEVEL = 1,
      PENDINGTYPE_NESTED = 2
    }
    enumPENDINGMSG
    {
      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)]
    interfaceIOleMessageFilter
    {
      [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>


Viewing all articles
Browse latest Browse all 3015