Async WindowsHook at WPF applications

Why we need async hook

If you are using WindowsHook in your application (SetWindowsHookEx winapi function) in order to handle global events from the Windows OS you should read this post to the end. Your application can affect whole system performance if you use such a hook in wrong way. When you catch global event at your application and process this message with delay then this delay will be visible end user at ALL other applications.

It is very user unfriendly and I want to tell you how we beat the problem

Why do hooks are needed?

Windows hook allows you to set desired filter for messages which Windows receives from all applications in the desktop. Such a filter allows you to execute custom code on for example mouse click at all applications. You can find a lot of information about it. There are several types of hook you can set. But it is not a topic of this post. Just refer SetWindowsHookEx WinApi function for more information.

Why you should avoid using hooks

Who say you shouldn’t? Is Microsoft documentation? You can use it! But… You should be aware that this can affect whole system so you should do it carefully. Especially if you execute complex logic on receiving such a hooked event. You can say – my handle logic is not so complex to affect system. But… Imaging you set Low Level Mouse Hook event (you hook handler method should proceed each mouse move event). Your application performs for example WebRequest and wait for response. How do you think how it can affect the system? Right, each mouse move event will be handled with timeout and it produces some kind of jerky mouse effect.

So what is the issue?

The issue is exists because of that fact that you set Windows Hook on the same thread as you heavy code (WebRequest from example above). So Windows message cannot be processed until WebRequest finished. System waits for a certain timeout and pass event to the target application. It provides some system response delay.

Simple async hook not so simple

Hook at another thread?
You possible want to say that just we should set a hook at another thread. Another thread? Yes but it is not all what you need! If you simply create another thread and set hook there you will be able to process hooked messages without any issue… until you try to perform any UI operation which affects message queue. Especially if you want to display you application and steal the focus (exactly what we need in our application)

Just another Thread? No! New UI thread with dispatcher
In order to process hooked messages properly you need new Thread with MessageQueue. You can create new Thread with classic WinForms MessageQueue but we work with WPF. We create new Thread and start there Dispatcher:

private void StartBackgroundUIThread()
{
Thread backgroundThread = new Thread(() =>
{
_backgroundDispatcher = Dispatcher.CurrentDispatcher;
_backgroundDispatcher.BeginInvoke(new Action(() =>
{
//setup windows hook here
};
Dispatcher.Run();
});
backgroundThread.IsBackground = true;
backgroundThread.SetApartmentState(ApartmentState.STA);
backgroundThread.Start();
}

It is enough? No! Let’s continue…

Cheating.. Cheating.. Cheating..

Now you are ready to activate your application form. And here you will see a lot of issues. When you receive hooked event you will be able to execute some code in Main UI thread (you need to Invoke it):

_uiContextDispatcher.Invoke(new Action(() =>;
{
//activate your form
this.Show();
this.Topmost = true;
this.Activate();
}), DispatcherPriority.Normal);

Show, Topmost, Activate… but you application will be shown not as active and won’t receive the focus. You will be able to see your application flashing in task bar. If you look into documentation you can find that Windows team introduces a bunch of restriction for such activation:

* The process is the foreground process.
* The process was started by the foreground process.
* The process received the last input event.
* There is no foreground process.
* The foreground process is being debugged.
* The foreground is not locked (see LockSetForegroundWindow).
* The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
* No menus are active.

A lot of, right? Is there any other way to activate your window? Sure!

Steal the focus? What can be easier!
It was an easy task… long long time ago. Now Windows internally prevents such a behavior. You can find additional details here. But there still is a cheat activation which allows you to join Message Queues (one from currently active and other from your application) and call SetForegroundWindow. This allows to share the focus, input state for you application. For me it was critical to call only this function and detach Message Queues connection:

IntPtr currentForegroundWindow = GetForegroundWindow();
IntPtr thisWindowHandle = new WindowInteropHelper(this).Handle;
uint thisWindowThreadId = GetWindowThreadProcessId(thisWindowHandle, IntPtr.Zero);
uint currentForegroundWindowThreadId = GetWindowThreadProcessId(currentForegroundWindow, IntPtr.Zero);
AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, true);
SetForegroundWindow(thisWindowHandle);
AttachThreadInput(currentForegroundWindowThreadId, thisWindowThreadId, false);

this.Show();
this.Activate();
//…do all stuff here

You can see here a lot of WinApi function but how without it )

Enough? Again No! What next?

Reading the manual and dancing with a tambourine

Separate assembly for Hook is required
You need to separate you hook event callback (method which received event from the system) into assembly. This is clearly specified at documentation. You will be able to work without such a separation but not when you using activation in additional thread.

hMod is required
It is possible to register hook without specifying module handle. And it even works with async hook. But not always.. but not under every Windows version. In order to make it platform independent just get module handle where your callback function is located:

IntPtr hMod = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]);
_hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, hMod, 0);

Last touch. Focus is required
You also need to call Window.Focus() after detaching Message Queues and Activation. Without it still no happiness. Just don’t forget.

Is it still laggy? What wrong?
Oh off course! Why the hell we created separate thread? Right! To process hooked event in async manner. So just change Invoke to BeginInvoke:

_uiContextDispatcher.BeginInvoke(new Action(() =>;
{
//use cheat activation here...
}), DispatcherPriority.Normal);

Now all? Yes!

Where do we use it? MouseExtender!

We are writing MouseExtender Launcher and it was very big problem because of complex rendering effects which slow Main UI thread and make mouse very jerky. Async hook helps us to be independent of Main UI thread, increase usability and make whole system more responsible.
This implementation already integrated into ME v 1.9


I think this post will be helpful. We put long long hours to solve this issue (and write this post :) ). We were asking Microsoft and WPF Community. Thank you for attention and sorry for such a long post. Somehow it is difficult for me to write briefly. But I promise to work on it :)

See you,
Alexey.

2 Responses

  1. Vova Says:

    Thank you very much for such a cool story!
    I see that all this difficulties arose because you mixed such stuff as hook, async handle and window activation in just one place. Am I right? If one piece of these stuff appears separatly then everything should be ok.

  2. Again new release ?? Yes afresh! MouseExtender 1.9.0.1 | MouseExtender Says:

    [...] half a year mouse extender followed by a several invincible bugs and evil mouse lags. Whole team put long hours to solve this issues. We waste a lot of time at forums and Microsoft support portals but without any result. What was [...]

Leave a Comment

*

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.