关闭

【C#】解决MouseHook捕获鼠标动作,在有些电脑上SetWindowsHookEx失败返回0的问题

标签: c#MouseHookSetWindows
1066人阅读 评论(0) 收藏 举报
分类:

最近在debug鼠标位置捕获的功能时发现在其中的一台开发电脑上,SetWindowsHookEx一直返回0,导致Hook设置失败,有时候调成Release模式又是正常的。代码如下:

hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure,Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0);

为什么一直返回0呢?微软也没有告诉我们具体原因,只让我们查询System Error Code

Type:
Type: HHOOK
If the function succeeds, the return value is the handle to the hook procedure.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.

通过文档里写的call GetLastError方法可以获取到error code。我这里的error code是126,查询对应文档发现详细错误是:

ERROR_MOD_NOT_FOUND
126 (0x7E)
The specified module could not be found.

即模块错误。

SetWindowHookEx中唯一跟模块有关的参数只有Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0])了。
在debug过程中,发现GetModules()[0]都是不为null的而且GetHINSTANCE也能获取到正确的值,实在不知道哪里的问题。不过经过不懈的搜索,发现StackOverflow里的大牛解决过这个问题(链接参考底部)。大概意思就是说在.Net4.0和Win8之前的版本中,CLR不再模拟托管程序集中的非托管句柄(我是.net4.0+win10不知为何也遇到了这个问题(lll¬ω¬))。建议我们用user32的句柄,而这个句柄会一直被.net加载。

所以 代码改动下就好了:

hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure,GetModuleHandle("user32"), 0);

完整代码参考:

class MouseHook
    {
        private const int WM_MOUSEMOVE = 0x200;
        private const int WM_LBUTTONDOWN = 0x201;
        private const int WM_RBUTTONDOWN = 0x204;
        private const int WM_MBUTTONDOWN = 0x207;
        private const int WM_LBUTTONUP = 0x202;
        private const int WM_RBUTTONUP = 0x205;
        private const int WM_MBUTTONUP = 0x208;
        private const int WM_LBUTTONDBLCLK = 0x203;
        private const int WM_RBUTTONDBLCLK = 0x206;
        private const int WM_MBUTTONDBLCLK = 0x209;

        public event MouseEventHandler OnMouseActivity;

        static int hMouseHook = 0;

        public const int WH_MOUSE_LL = 14;//low level mouse event
        public const int WH_MOUSE = 7;//normal level mouse event

        HookProc MouseHookProcedure;
        Log _log = new Log("MouseHook", true, Log4netWrapper.Default);
        [StructLayout(LayoutKind.Sequential)]
        public class POINT
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class MouseHookStruct
        {
            public POINT pt;
            public int hWnd;
            public int wHitTestCode;
            public int dwExtraInfo;
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int GetLastError();

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("kernel32.dll")]
        private static extern int GetCurrentThreadId();//获取在系统中的线程ID

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);


        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);


        public MouseHook()
        {
        }

        ~MouseHook()
        {
            Stop();
        }

        public void Start(int threadId)
        {
            if (hMouseHook == 0)
            {
                MouseHookProcedure = new HookProc(MouseHookProc);

                hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, GetModuleHandle("user32"), 0);//第一个参数是WH_MOUSE_LL,表示捕获所有线程的鼠标消息,同时最后一个参数必须是0
                //hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookProcedure, GetModuleHandle("user32"), GetCurrentThreadId());//只捕获当前应用程序(当前线程)的鼠标消息,最后一个参数是当前线程id,使用GetCurrentThreadId()获得,一定不要使用托管线程id(Thread.CurrentThread.ManagedThreadId)。
                if (hMouseHook == 0)
                {
                    int errorCode = GetLastError();
                    _log.E("SetWindowsHookEx failed.error code:" + errorCode);
                    Stop();
                }
            }
        }

        public void Stop()
        {
            bool retMouse = true;
            if (hMouseHook != 0)
            {
                retMouse = UnhookWindowsHookEx(hMouseHook);
                hMouseHook = 0;
            }

            if (!(retMouse))
            {
                _log.E("UnhookWindowsHookEx failed.");
            }
        }

        private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            //只处理鼠标左键按下的情况
            if ((wParam == WM_LBUTTONDOWN) && (nCode >= 0) && (OnMouseActivity != null))
            {
                MouseButtons button = MouseButtons.None;
                int clickCount = 0;

                switch (wParam)
                {
                    case WM_LBUTTONDOWN:
                        button = MouseButtons.Left;
                        clickCount = 1;
                        break;
                    case WM_LBUTTONUP:
                        button = MouseButtons.Left;
                        clickCount = 1;
                        break;
                    case WM_LBUTTONDBLCLK:
                        button = MouseButtons.Left;
                        clickCount = 2;
                        break;
                    case WM_RBUTTONDOWN:
                        button = MouseButtons.Right;
                        clickCount = 1;
                        break;
                    case WM_RBUTTONUP:
                        button = MouseButtons.Right;
                        clickCount = 1;
                        break;
                    case WM_RBUTTONDBLCLK:
                        button = MouseButtons.Right;
                        clickCount = 2;
                        break;
                }

                MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
                MouseEventArgs e = new MouseEventArgs(button, clickCount, MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y, 0);
                OnMouseActivity(this, e);
            }
            return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
        }
    }

使用方法:

MouseHook hook = new MouseHook();
hook.OnMouseActivity += Hook_OnMouseActivity;
hook.Start();

 private void Hook_OnMouseActivity(object sender, System.Windows.Forms.MouseEventArgs e)
{
  //e.X  e.Y   e.Button == System.Windows.Forms.MouseButtons.Left
}

当程序关闭或者使用结束时一定要调用,hook.Stop()卸载掉钩子,不然可能会出现蓝屏、死机之类的系统问题。


2017-12-19更新
最后在使用过程中发现,在click操作中偶尔会出现鼠标指针卡顿的情况,google了一下大概是low level的钩子是否响应取决于你主线程是否响应,在click过程中,我的主线程确实会卡一下,所以就鼠标指针就会有点跳。解决方法就是新起一个线程安装钩子:

ThreadPool.QueueUserWorkItem(SetHK);
//...
private void SetHK(object state)
        {
            hook = new MouseHook();
            hook.OnMouseActivity += Hook_OnMouseActivity;
            if (StringConstant.Build)
            {
                hook.Start(Thread.CurrentThread.ManagedThreadId);
                tagMSG Msgs;
                while (GetMessage(out Msgs, IntPtr.Zero, 0, 0) > 0)
                {
                    TranslateMessage(ref Msgs);
                    DispatchMessage(ref Msgs);
                }
            }
        }

其中里面的tagMSG与Translatemessage对应:

#region Hook
        [DllImport("user32", EntryPoint = "GetMessage")]
        public static extern int GetMessage(out tagMSG lpMsg, IntPtr hwnd, int wMsgFilterMin, int wMsgFilterMax
        );
        [DllImport("user32", EntryPoint = "DispatchMessage")]
        public static extern int DispatchMessage(ref tagMSG lpMsg);
        [DllImport("user32", EntryPoint = "TranslateMessage")]
        public static extern int TranslateMessage(ref tagMSG lpMsg);
        [StructLayout(LayoutKind.Sequential)]
        public class POINT
        {
            public int x;
            public int y;
        }
        public struct tagMSG
        {
            public int hwnd;
            public uint message;
            public int wParam;
            public long lParam;
            public uint time;
            public int pt;
        }
        MouseHook hook;
        #endregion

参考链接

1. SetWindowsHookEx function
2. Runtime Error 126 - The specified module could not be found
3. Global mouse event handler
4. C#钩子函数放在线程里钩不上的解决办法

0
0
查看评论

C#使用全局钩子(hook),SetWindowsHookEx返回0、不回调的解决

在.net 2005平台下 在使用全局hook时,总是遇见SetWindowsHookEx的返回值为0,而且在: DllImport的SetLastError选项 = true的时候,调用Marshal.GetLastWin32Error() 也返回0,那么就看看是否这个原因:因为VS的调试模式的...
  • changtianshuiyue
  • changtianshuiyue
  • 2013-07-13 09:57
  • 4699

C#编程技巧之钩子函数的使用——SetWindowsHookEx

文章来源:http://www.cnblogs.com/Johness/archive/2012/12/28/2837977.html 总所周知:C#是.NET Framework平台的相伴语言,用它本身的类库和编译器提供的方法是无法实现全局钩子的。但实际上对于非托管代码的调用在C#中是成立的,...
  • u013075699
  • u013075699
  • 2015-05-15 21:16
  • 1466

c#使用钩子拦截鼠标键盘事件

窗体本身带的键盘鼠标事件函数只能响应窗体自己的事件,窗体之外的事件是不会响应的。比如当窗体最小化的时候也响应就要用全局钩子拦截消息来处理了。 大概过程就是在窗体初始化时加载钩子,等待事件消息,事件触发后调用响应函数处理。 using System; using System.Collections....
  • gaiazhang
  • gaiazhang
  • 2016-12-30 14:35
  • 1127

C#钩子类 几乎捕获键盘鼠标所有事件

using System; using System.Text; using System.Runtime.InteropServices; using System.Reflection; using System.Windows.Forms; namespace MouseKeyboardLi...
  • smartsmile2012
  • smartsmile2012
  • 2014-05-07 10:39
  • 12606

SetWindowsHookEx 失败的一个隐晦的错误原因(思路为主,解决为辅)

我在上一篇博客《SetWindowsHookEx 的资料整理与内部机理的深入分析》中详细介绍了SetWindowsHookEx的失败原因,今天又发现了一个隐晦的错误原因,这也是我这么多天来一直在寻找的东西。起因是我想为一个进程的所有线程都调用SetWindowsHookEx,现象是有些线程可以调用成...
  • on_1y
  • on_1y
  • 2012-05-19 19:29
  • 7776

C# winform键盘钩子

http://blog.163.com/da7_1@126/blog/static/1040726782011112652629750/ 新建一个工程CSharpKeyboardHook,在WinForm窗体上拉一个Lable(用于显示,按下的是那个键)和两个button(用于开关勾子) 下...
  • az44yao
  • az44yao
  • 2015-08-03 10:05
  • 2715

WH_MOUSE 与WM_MOUSE_LL的区别

<br />坏境:windows XP<br />在使用SetWindowsHook截获鼠标事件时,如果使用WH_MOUSE参数,则只能监控钩子所在模块的鼠标事件。<br />如需要截获整个系统的鼠标事件,那么使用WH_MOUSE_LL参数。<br />...
  • TBWood
  • TBWood
  • 2011-05-30 18:13
  • 9198

低级鼠标钩子WH_MOUSE_LL

翻MSDN可知,鼠标钩子类型有两个,一个是WH_MOUSE,另一个是WH_MOUSE_LL, 至于它们的区别,百度了一下: 1.WH_MOUSE只能监控钩子所在模块的鼠标事件。 2.WH_MOUSE_LL可以截获整个系统所有模块的鼠标事件。 -----------------------------...
  • friendan
  • friendan
  • 2013-09-29 18:57
  • 13840

SetWindowsHookEx设置全局钩子

用途该函数可以在R3下钩子,可监控键盘、鼠标、窗口等各种消息。监控到后调用指定的回调函数。函数原型HHOOK WINAPI SetWindowsHookEx( __in int idHook, \\钩子类型 __in HOOKPROC lpfn, \\回调函数地址 __in HINSTANCE hM...
  • zyorz
  • zyorz
  • 2017-05-02 19:43
  • 641

SetWindowsHookEx

作用:将应用程序定义的钩子过程安装到钩子链中。您将安装一个挂钩程序来监视系统的某些类型的事件。这些事件与特定线程或与调用线程在同一桌面中的所有线程相关联。 HHOOK WINAPI SetWindowsHookEx( _In_ int idHook, _In_ HOOKPR...
  • swartz_lubel
  • swartz_lubel
  • 2017-07-08 23:48
  • 267
    个人资料
    • 访问:206177次
    • 积分:2788
    • 等级:
    • 排名:第14688名
    • 原创:90篇
    • 转载:18篇
    • 译文:1篇
    • 评论:36条
    文章分类
    最新评论