none
Running mousehook in background MTA thread issue RRS feed

  • Question

  • I am running various processes in a background MTA thread with a message queue, 1 of the messages starts a global mousehook on the background thread. On clicking the mouse the UI is updated on the main thread. On starting the mousehook my application pretty much locks up the mouse so it loses its smooth movement and just jumps occasionally in the direction you are moving it. However I discovered purely by chance when putting in the message box on the background thread as shown in the code below that once the message box has appeared (and is then left alone) the mousehook works perfectly on the background thread. Problem is I can't bring up a message box as part of the app :-). Some of the code is taken from a sample which is primarily for UIA with a message queue except I have modified it to run the mouse hook on the background thread. At the moment the message queue is overkill. I expect you will want to know why I want to run a global mousehook on a background thread in the 1st place, it's a long story but basically this is working perfectly for my needs except for the message box. The problem is along the lines that the message box is keeping the background thread open to process the mouse clicks so there has to be a proper way of doing this. An explanation would be great.

    As I said previously the message queue code and background MTA thread example is taken from a UIA sample and I am trying to use it to run the mousehook on a background thread. It works perfectly except the message box. Hope this all makes sense. Thank you for any help.

    // Main Thread private MouseProcessor mouseProcessor; public delegate void MouseActionUpdateDelegate(); // Main thread dispatcher, public Dispatcher controllerDispatcher; mouseProcessor = new MouseProcessor(); // UpdateUI updates the user interface on the main thread

    //after the background thread has done the processing mouseProcessor.Initialize(new MouseActionUpdateDelegate(UpdateUI), controllerDispatcher); mouseProcessor.InformBackgroundThreadToStartMouseHook(); //Mouse Processor Class using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Threading; using System.Threading; namespace MyApp { class MouseProcessor { private Dispatcher _controllerDispatcher; // Dispatcher.BeginInvoke to notify the main UI thread private Thread _threadBackground; // Background MTA thread on which all calls to UIA are made. private AutoResetEvent _autoEventMsg; // Event used to notify the background thread that action is required. private AutoResetEvent _autoEventInit; // Event used to allow background thread to take any required initialization action. // SampleMsgType and SampleMsgData are used to allow the main UI thread to communicate that specific action is required by the background thread. private enum SampleMsgType { msgNull, msgStartMouseHook, msgCloseDown }; private struct SampleMsgData { public SampleMsgType msgType; }; // doesn't really need to support a queue of messages for action required by the // background thread, but use a queue anyway in case requirements change in the future. private Queue<SampleMsgData> _msgQueue = new Queue<SampleMsgData>(); private MyApp.MouseActionUpdateDelegate _UpdateUIOnMouseActionDelegate; public void Initialize(MyApp.MouseActionUpdateDelegate updateUIOnMouseActionDelegate, Dispatcher controllerDispatcher) { _UpdateUIOnMouseActionDelegate = updateUIOnMouseActionDelegate; _controllerDispatcher = controllerDispatcher; // The object will call UIA on a background MTA thread. This app doesn't // expect to enter here with a background thread already initialized. if (_threadBackground != null) { return; } // The background thread will wait for notifications when action is required. _autoEventMsg = new AutoResetEvent(false); // Create the background thread, and wait until it's ready to start working. _autoEventInit = new AutoResetEvent(false); ParameterizedThreadStart paramThreadStart = new ParameterizedThreadStart(s_DoWork); _threadBackground = new Thread(paramThreadStart); _threadBackground.SetApartmentState(ApartmentState.MTA); _threadBackground.Start(this); _autoEventInit.WaitOne(); } public void Uninitialize() { // Tell the background thread to close down. if (_threadBackground != null) { SampleMsgData msgData = new SampleMsgData(); msgData.msgType = SampleMsgType.msgCloseDown; AddMsgToQueue(msgData); } } private void AddMsgToQueue(SampleMsgData msgData) { // Request the lock, and block until it is obtained. Monitor.Enter(_msgQueue); try { // When the lock is obtained, add an element. _msgQueue.Enqueue(msgData); } finally { // Ensure that the lock is released. Monitor.Exit(_msgQueue); } // Let the background thread know there's some action to be taken. _autoEventMsg.Set(); } private static void s_DoWork(object data) { MouseProcessor mouseProcessor = (MouseProcessor)data; mouseProcessor.ThreadProc(); } private void ThreadProc() { // *** Note: The thread on which the UIA calls are made below must be MTA. // Let the main thread know this thread is ready to start processing. _autoEventInit.Set(); // fCloseDown will be set true when the thread is to close down. bool fCloseDown = false; while (!fCloseDown) { // Wait here unt(il we're told we have some work to do. _autoEventMsg.WaitOne(); while (true) { SampleMsgData msgData; // Note that none of the queue or message related action here is specific to UIA. // Rather it is only a means for the main UI thread and the background MTA thread // to communicate. // Get a message from the queue of action-related messages. Monitor.Enter(_msgQueue); try { // An exception is thrown when the queue is empty. msgData = _msgQueue.Dequeue(); } catch (InvalidOperationException) { // InvalidOperationException is thrown if the queue is empty. msgData.msgType = SampleMsgType.msgNull; break; } finally { // Ensure that the lock is released. Monitor.Exit(_msgQueue); } // Process the request depending on message type coming from the queue switch (msgData.msgType) { case SampleMsgType.msgStartMouseHook: { InitiateMouseHook(); break; } case SampleMsgType.msgCloseDown: { // The main UI thread is telling this background thread to close down. fCloseDown = true; break; } } } } } public void InformBackgroundThreadToStartMouseHook() { SampleMsgData msgData = new SampleMsgData(); msgData.msgType = SampleMsgType.msgStartMouseHook; AddMsgToQueue(msgData); } private void InitiateMouseHook() { // Set up global mouse hooks to react to mouse clicks try { MouseHook.MouseAction += new EventHandler(MouseHook_MouseAction); MouseHook.Start(); //Without calling the message box below the mousehook doesn't work as described above. With the message box displayed it works perfectly as described above. System.Windows.MessageBox.Show("","Testing", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } catch (Exception exc) { System.Windows.MessageBox.Show("PROBLEM", "Testing", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } private void MouseHook_MouseAction(object sender, EventArgs e) { _controllerDispatcher.BeginInvoke(_UpdateUIOnMouseActionDelegate); } } }


    • Edited by scocia888 Saturday, March 21, 2020 1:41 PM
    Saturday, March 21, 2020 12:35 PM

All replies

  • Are you talking about a low-level mousehook as described at LowLevelMouseProc callback function?  If so, the thread on which the hook was installed needs a message pump.

    Perhaps that is the reason why the message box seems to influence the results of processing.

    Saturday, March 21, 2020 5:21 PM
  • Yes that is correct it's a LowLevelMouseProc, can you help further with implementing a message pump in the class above.

    private static IntPtr SetHook(LowLevelMouseProc proc)
            {
                IntPtr hook = IntPtr.Zero;
               
                    using (Process curProcess = Process.GetCurrentProcess())
                    using (ProcessModule curModule = curProcess.MainModule)
                    {
                        hook = SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle("user32"), 0);
                        if (hook == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
                        
                    }
                    
               
                return hook;
            }

    Saturday, March 21, 2020 5:37 PM
  • Well, the simplest message pump in unmanaged C code would look like this -

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }

    The while loop would exit when a WM_QUIT message is processed.  For a thread, this would typically be done by posting the WM_QUIT message to the thread's windows message queue with the PostThreadMessage API function.


    • Edited by RLWA32 Saturday, March 21, 2020 5:48 PM
    Saturday, March 21, 2020 5:47 PM
  • Thanks and while I understand that this is what Win32 is all about in terms of the message loop/pump I still don't know how to implement in the class above using C#? 
    Saturday, March 21, 2020 5:59 PM
  • This is a complete class for the global mousehook if anyone wants to see the effect of the results box.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System.Threading;
    using System.Windows.Threading;
    
    namespace MyApp
    {
        public static class MouseHook
        {
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern IntPtr SetWindowsHookEx(int idHook,
              LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool UnhookWindowsHookEx(IntPtr hhk);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
              IntPtr wParam, IntPtr lParam);
    
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern IntPtr GetModuleHandle(string lpModuleName);
    
            public static event EventHandler MouseAction = delegate { };
    
            private const int WH_MOUSE_LL = 14;
    
            private static Dispatcher mouseDispatcher;
            public delegate void del();
    
            public static Boolean MouseStopped = false;
    
            private enum MouseMessages
            {
                WM_LBUTTONDOWN = 0x0201,
                //  WM_LBUTTONUP = 0x0202,
                //  WM_MOUSEMOVE = 0x0200,
                //  WM_MOUSEWHEEL = 0x020A,
                WM_RBUTTONDOWN = 0x0204,
                // WM_RBUTTONUP = 0x0205*/
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct POINT
            {
                public int x;
                public int y;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            private struct MSLLHOOKSTRUCT
            {
                public POINT pt;
                public uint mouseData;
                public uint flags;
                public uint time;
                public IntPtr dwExtraInfo;
            }
    
            public static void Start()
            {
                _hookID = SetHook(_proc);
    
            }
            public static void stop()
            {
                UnhookWindowsHookEx(_hookID);
            }
    
            private static LowLevelMouseProc _proc = HookCallback;
            private static IntPtr _hookID = IntPtr.Zero;
    
            private static IntPtr SetHook(LowLevelMouseProc proc)
            {
                IntPtr hook = IntPtr.Zero;
               
                    using (Process curProcess = Process.GetCurrentProcess())
                    using (ProcessModule curModule = curProcess.MainModule)
                    {
                        hook = SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle("user32"), 0);
                        if (hook == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
                        
                    }
                    
               
                return hook;
            }
    
            private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
    
            private static IntPtr HookCallback(
              int nCode, IntPtr wParam, IntPtr lParam)
            {
    
                if (nCode >= 0 && (MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam || MouseMessages.WM_RBUTTONDOWN == (MouseMessages)wParam))
                {
    
                    MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
                    MouseAction(null, new EventArgs());
    
                    // stop();
                    //  MouseStopped = true;
                }
    
    
                return CallNextHookEx(_hookID, nCode, wParam, lParam);
            }
        }
    }
    
    
        

    Saturday, March 21, 2020 6:02 PM
  • You would implement the message pump in the thread procedure using P/invoke.
    Saturday, March 21, 2020 6:02 PM
  • Another thing to think about is running a message pump on a thread in an MTA apartment.  The two aren't generally used together.  Have a look at User interface code + multi-threaded apartment = death
    Saturday, March 21, 2020 6:08 PM
  • Okay thanks I will have a look at P/Invoke on these Win32 API calls and read what RC has to say which will no doubt be informative.
    Saturday, March 21, 2020 6:36 PM