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>