SetWindowsHookEx with WH_MOUSE_LL slows down the mouse for several seconds
Asked Answered
C

6

6

I am using the following code to get mouse messages on the current process.

using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
    return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}

For some reason when this code runs the mouse get slow for several seconds and then back to normal.

Any ideas?
Thanks

EDIT - hook method

private static IntPtr mouseEvent(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0 && MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam)
    {
        MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));     
        LastLeftClick = new ClickInfo { Time = DateTime.Now, X = hookStruct.pt.x, Y = hookStruct.pt.y };
    }
    return CallNextHookEx(hookID, nCode, wParam, lParam);
}

public class ClickInfo
{
    public int X { get; set; }
    public int Y { get; set; }
    public DateTime Time { get; set; }
}
Cacus answered 12/7, 2010 at 12:1 Comment(6)
does this also happen in Release mode?Spielman
@Patrick Klug: Also on release mode.Cacus
WH_MOUSE_LL requires Windows to forward all mouse events to your application, wait for them to be processed, and then continue as normal. Mouse messages - especially if you have a high-resolution mouse - come fast and furious: it doesn't take long for them to pile up if they're not quickly dispatched. And the first time your callback is called, it likely won't be responding quickly: your process must first perform its own processing and then the runtime must JIT your callback and the P/Invoke call to CallNextHookEx().Zero
Two things to check: 1) watch the performance of your process during the time the hook is being installed (you may want to arrange for the hook to be installed automatically during an otherwise "quiet" period to make this easier). If you're not pumping messages quickly enough, it won't take much for you to cause a backlog. 2) try prejitting your callback by calling System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod().Zero
@Shog9: Pre-Jitting didn't help. I'll try to check the performance when I will find the time. Thanks for your help :)Cacus
Be sure you start your message-loop. At first I didn't realize I have to call Application.Run() (to start the message-loop) and my mouse lagged because of it too. Dunno why exactly that is, but I'm too lazy to figure it out now that it's not a problem anymore. :DBeadle
B
3

What does your hook procedure look like?

If your process only has one UI thread, use a Message Filter instead: http://msdn.microsoft.com/en-us/library/system.windows.forms.application.addmessagefilter.aspx

Bencher answered 12/7, 2010 at 13:22 Comment(4)
Why does it matter how does the procedure looks like if the problem is not in the mouse event process but on the registration itself?Cacus
Unless you are careful not to move the mouse at all during registration (is it invoked in response to a button click?), and you have a stable mouse (some mice, especially ball-less mice, are noisy), your hook procedure may be being invoked immediately.Bencher
I guess the hook is being invoked immediately but the slowdown is only on registration and not on other hook calls.Cacus
Right.. so what's your hook procedure doing? Is it making static calls which might invoke a static ctor the first time?Bencher
A
4

I had the same problem (only it's c++ project, not c#) and resolved it by changing hook from WH_MOUSE_LL to WH_MOUSE (from low-level to normal level). For WM_LBUTTONUP and WM_RBUTTONUP messages it works ok.

The thing that amuses me is that code with WH_MOUSE_LL was doing fine at the time I wrote it (no mouse freezes etc.) It seems like some security update for Windows changed the behavior of the mouse hooks and previously fine code become a problem.

Ajani answered 19/7, 2010 at 23:28 Comment(0)
K
4

I am sorry to follow up after such a long time but I have solved the issue by spawning a separate thread which handles the hook (I have not added everything to the code as it also translates messages but the main idea should be clear):

    public Form1()
    {
        InitializeComponent();

        Thread thread = new Thread(HookThread);
        thread.IsBackground = true;
        thread.Start();
    }

    private void HookThread()
    {
        _hookControl = new Control();
        IntPtr handle = _hookControl.Handle;

        _hookProc = new HookProc(HookFunction);
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            _hook = SetWindowsHookEx(HookType.WH_MOUSE_LL, _hookProc, GetModuleHandle(curModule.ModuleName), 0);// (uint)AppDomain.GetCurrentThreadId());
        }

        Application.Run();

        UnhookWindowsHookEx(_hook);
        _hook = IntPtr.Zero;
    }

    private IntPtr HookFunction(int code, IntPtr wParam, IntPtr lParam)
    {
        if (code < 0)
        {
            //you need to call CallNextHookEx without further processing
            //and return the value returned by CallNextHookEx
            return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
        }

        int msg = wParam.ToInt32();
        string messages = string.Join(", ", _messageMapping.Where(t => t.Item1 == msg).Select(t => t.Item2));
        if (string.IsNullOrWhiteSpace(messages))
            messages = msg.ToString();
        Trace.WriteLine($"Messages: { messages }");

        //return the value returned by CallNextHookEx
        return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
    }

You can terminate the thread from any other thread by calling BeginInvoke on the created _hookControl:

        _hookControl.BeginInvoke(((Action)(() => Application.ExitThread())));
Kunming answered 6/9, 2018 at 10:26 Comment(0)
B
3

What does your hook procedure look like?

If your process only has one UI thread, use a Message Filter instead: http://msdn.microsoft.com/en-us/library/system.windows.forms.application.addmessagefilter.aspx

Bencher answered 12/7, 2010 at 13:22 Comment(4)
Why does it matter how does the procedure looks like if the problem is not in the mouse event process but on the registration itself?Cacus
Unless you are careful not to move the mouse at all during registration (is it invoked in response to a button click?), and you have a stable mouse (some mice, especially ball-less mice, are noisy), your hook procedure may be being invoked immediately.Bencher
I guess the hook is being invoked immediately but the slowdown is only on registration and not on other hook calls.Cacus
Right.. so what's your hook procedure doing? Is it making static calls which might invoke a static ctor the first time?Bencher
B
2

By setting a low level hook responsiveness of the mouse now becomes dependent on your main thread being responsive and a common mistake made is to set the hook early in the start-up process.

SetHook
LongRunningStartupProcess
Mouse isn’t responsive until here

During start-up it is best to dispatch the hook onto the thread so it happens at the end of the start-up process. Dispatcher.CurrentDispatcher.BeginInvoke(new Action(SetHook));

DispatchSetHook
LongRunningStartupProcess
SetHook(callback)
Mouse becomes responsive

This still has ongoing management issues within your application to ensure the main thread doesn’t do any long running processes as that will lock the mouse up too. This can easily be verified by setting the hook and then doing a Thread.Sleep on the main thread.

Brownlee answered 21/10, 2011 at 0:37 Comment(0)
F
1

Your hook proc is expensive; you just need to figure out why and how to fix it.

Even though though the code looks very minimal i suspect that there is some initial C# interop expense triggering the delays, perhaps due to JIT or paging.

If you change the code to do as much processing as possible off of this thread the problem should go away. as a C++ developer I even worry about the Marshal.PtrToStructure since low-level hooks are very sensitive and I can't say off the top of my head that this operation is guaranteed to be so cheap that it wouldn't impair mouse movement.

I've used low-level mouse hooks quite a bit in the past (in C++) and have never had problems unless the hook procedure itself is expensive. In C++ I try to avoid doing anything more than a PostMessage to an HWND that does the rest of the processing.

Fatuitous answered 20/7, 2010 at 4:50 Comment(2)
IMHO if the hook was the problem I should be experiencing the problem on every hook call (which is basically the living time of my process). As for your first suggestion - do you know of any way to investigate this issue?Cacus
probably the best way to get to the root cause is to use a tool like 'xperf' to view what is happening on the system. this should give you direct insight into what's happening at the point where the mouse stalls. msdn.microsoft.com/en-us/performance/cc825801.aspx it is not the easiest tool in the world to use, but it's not that hard either, and it's free.Fatuitous
E
1

When you receive the hook event, turn off the book, then do your work, and if really still needed, put the hook back on.

This will stop the mouse from lagging.

Only stay hooked when you really need to.

Erosive answered 24/2, 2011 at 11:40 Comment(1)
I doubt it will help since the mouse is lagging only when I first set the hook.Cacus

© 2022 - 2024 — McMap. All rights reserved.