Reputation: 2521
I have a problem with a system we're using with our application: Sometimes, when we ask this system for data he pops a MessageBox up to tell us something along the lines of: "I can't retrieve your data, there is way too much data to search for".
The problem with this is that the user may be unable to see or close the popup and thus this blocks the whole application (Explaining why the user cannot close/see the popup would take too much time and be off topic, that sucks but we've to deal with it).
So, as a temporary solution I would like to prevent this specific process from creating the MessageBox.
I've looked for a solution online and found about CBTProc which seems to provide a way to react to a particular Windows event (request from a process to create a window) and instruct the OS to block the request.
Is this the way to go?
I tested SetWinEventHook to detect process that request creation of a window and DestroyWindow to destroy the Window:
public class PopupWatchdog {
#region constructor
public PopupWatchdog() {
SetWinEventHook(
EVENT_OBJECT_CREATED,
EVENT_OBJECT_CREATED,
IntPtr.Zero,
HookCallback,
0, //id process
0, //id thread
WINEVENT_OUTOFCONTEXT
);
}
#endregion
#region functions
private static void HookCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) {
Console.WriteLine("window {0} requests creating an object, trying to destroy it...", idChild);
DestroyWindow(hWnd);
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWinEventHook(int eventMin, int eventMax, IntPtr hmodWinEventProc, HookProc lpfnWinEventProc, int idProcess, int idThread, int dwflags);
[DllImport("user32.dll")]
private static extern bool DestroyWindow(IntPtr hWnd);
#endregion
#region events
#endregion
#region variables
#region const
private const int EVENT_OBJECT_CREATED = 0x8000;
private const int WINEVENT_OUTOFCONTEXT = 0;
#endregion
private delegate void HookProc(
IntPtr hWinEventHook,
int iEvent,
IntPtr hWnd,
int idObject,
int idChild,
int dwEventThread,
int dwmsEventTime
);
#endregion
}
DestroyWindow cannot be used to destroy Window created by another Thread like msdn documentation says, which is understable. So my test were not successful. How can I work this out?
I may have made mistakes, I don't know Windows api well and just heard about CBTProc.
Update
I changed the code following @DavidHeffernan and @AlexK advices, and it works:
public class BlockingPopupWatchdog {
#region ctor
public BlockingPopupWatchdog(int processId) {
_processId = processId;
}
#endregion
#region functions
internal bool Hook() {
if (_hookId != IntPtr.Zero) {
Unhook();
}
_hookId = SetWinEventHook(
EVENT_OBJECT_CREATED,
EVENT_OBJECT_CREATED,
IntPtr.Zero,
_hook,
_processId, //id process
0, //id thread
WINEVENT_OUTOFCONTEXT
);
if (_hookId == IntPtr.Zero) {
Logger.Log(String.Format("Error {0} while hooking", Marshal.GetLastWin32Error()), EventTypes.WARNING);
return false;
}
return true;
}
internal bool Unhook() {
if (_hookId == IntPtr.Zero) return false;
if (!UnhookWinEvent(_hookId)) {
Logger.Log(String.Format("Error {0} while unhooking", Marshal.GetLastWin32Error()), EventTypes.WARNING);
return false;
}
return true;
}
private static void HookCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) {
if (hWnd == IntPtr.Zero) return;
try {
AutomationElement elem = AutomationElement.FromHandle(hWnd);
if (elem == null || !elem.Current.ClassName.Equals(MESSAGEBOX_CLASS_NAME)) {
return;
}
object pattern;
if (!elem.TryGetCurrentPattern(WindowPattern.Pattern, out pattern)) return;
WindowPattern window = (WindowPattern)pattern;
if (window.Current.WindowInteractionState == WindowInteractionState.ReadyForUserInteraction) {
window.Close();
}
} catch (Exception e) {
Console.WriteLine(e);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWinEventHook(int eventMin, int eventMax, IntPtr hmodWinEventProc, HookProc lpfnWinEventProc, int idProcess, int idThread, int dwflags);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
#endregion
#region variables
#region const
private const String MESSAGEBOX_CLASS_NAME = "#32770";
private const int EVENT_OBJECT_CREATED = 0x8000;
private const int WINEVENT_OUTOFCONTEXT = 0;
#endregion
private delegate void HookProc(
IntPtr hWinEventHook,
int iEvent,
IntPtr hWnd,
int idObject,
int idChild,
int dwEventThread,
int dwmsEventTime
);
private static readonly HookProc _hook = HookCallback;
private IntPtr _hookId;
private readonly int _processId;
#endregion
}
Upvotes: 2
Views: 1016
Reputation: 2521
Thanks to DavidHefferman and AlexK. here is the solutions that makes what I wanted.
Using WinApi:
public class BlockingPopupWatchdog {
#region ctor
public BlockingPopupWatchdog(int processId) {
_processId = processId;
}
#endregion
#region functions
internal bool Hook() {
if (_hookId != IntPtr.Zero) {
Unhook();
}
_hookId = SetWinEventHook(
EVENT_OBJECT_CREATED,
EVENT_OBJECT_CREATED,
IntPtr.Zero,
_hook,
_processId, //id process
0, //id thread
WINEVENT_OUTOFCONTEXT
);
if (_hookId == IntPtr.Zero) {
Logger.Log(String.Format("Error {0} while hooking", Marshal.GetLastWin32Error()), EventTypes.WARNING);
return false;
}
return true;
}
internal bool Unhook() {
if (_hookId == IntPtr.Zero) return false;
if (!UnhookWinEvent(_hookId)) {
Logger.Log(String.Format("Error {0} while unhooking", Marshal.GetLastWin32Error()), EventTypes.WARNING);
return false;
}
return true;
}
private static void HookCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) {
if (hWnd == IntPtr.Zero) return;
try {
AutomationElement elem = AutomationElement.FromHandle(hWnd);
if (elem == null || !elem.Current.ClassName.Equals(MESSAGEBOX_CLASS_NAME)) {
return;
}
object pattern;
if (!elem.TryGetCurrentPattern(WindowPattern.Pattern, out pattern)) return;
WindowPattern window = (WindowPattern)pattern;
if (window.Current.WindowInteractionState == WindowInteractionState.ReadyForUserInteraction) {
window.Close();
}
} catch (Exception e) {
Console.WriteLine(e);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWinEventHook(int eventMin, int eventMax, IntPtr hmodWinEventProc, HookProc lpfnWinEventProc, int idProcess, int idThread, int dwflags);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
#endregion
#region variables
#region const
private const String MESSAGEBOX_CLASS_NAME = "#32770";
private const int EVENT_OBJECT_CREATED = 0x8000;
private const int WINEVENT_OUTOFCONTEXT = 0;
#endregion
private delegate void HookProc(
IntPtr hWinEventHook,
int iEvent,
IntPtr hWnd,
int idObject,
int idChild,
int dwEventThread,
int dwmsEventTime
);
private static readonly HookProc _hook = HookCallback;
private IntPtr _hookId;
private readonly int _processId;
#endregion
}
And the solution using UIAutomation:
private AutomationElement _watchedElement;
private void PopupOpenedHandler(Object sender, AutomationEventArgs args) {
var element = sender as AutomationElement;
if (element == null || !element.Current.ClassName.Equals(MESSAGEBOX_CLASS_NAME)) {
return;
}
object pattern;
if (!element.TryGetCurrentPattern(WindowPattern.Pattern, out pattern)) return;
WindowPattern window = (WindowPattern)pattern;
if (window.Current.WindowInteractionState == WindowInteractionState.ReadyForUserInteraction) {
window.Close();
}
}
internal bool Hook() {
Process p = Process.GetProcessById(_processId);
IntPtr wHnd = p.MainWindowHandle;
if (wHnd != IntPtr.Zero) {
_watchedElement = AutomationElement.FromHandle(wHnd);
Automation.AddAutomationEventHandler (
WindowPattern.WindowOpenedEvent,
_watchedElement,
TreeScope.Descendants,
PopupOpenedHandler
);
return true;
}
return false;
}
internal bool Unhook() {
if (_watchedElement == null) return false;
Automation.RemoveAutomationEventHandler(WindowPattern.WindowOpenedEvent, _watchedElement, PopupOpenedHandler);
return true;
}
Upvotes: 2