当前位置: 首页 > ai >正文

【C#】解决MouseHook捕获鼠标动作,在有些电脑上SetWindowsHookEx失败返回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 eventpublic const int WH_MOUSE = 7;//normal level mouse eventHookProc 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(){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

2018-01-19更新
如果在Winform或者WPF程序中使用“线程钩子”,因为当前操作可能不会在安装的那个线程上,所以会引起偶尔失效的问题。在winform中建议使用Application.AddMessageFilter(),在wpf中使用ComponentDispatcher.ThreadFilterMessage


参考链接

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

http://www.xdnf.cn/news/11654.html

相关文章:

  • Simsimi 小黄鸡机器人最新无限制接口api simsimi机器人接口api 微信公众号
  • 关闭QQ弹出的腾讯网迷你版
  • 教你使用Anti ARP Sniffer查找ARP攻击者
  • Mocha Doom 使用指南
  • Visual Studio 2010旗舰版正式版序列号 - civilman的专栏 - 博客频道 - CSDN.NET
  • Tomcat宕机检测以及宕机自启方法
  • QQ农场外挂开发实践
  • 八种常见视频格式转换详解
  • html网页制作代码大全——大学生影视主题网页制作——图图影视影院5页HTML+CSS+JavaScript
  • 最全Android学习路线总结,绝对干货
  • (附源码)SSM人事管理系统的设计和实现 计算机毕设48260
  • python 快速排序 详解_Python实现的数据结构与算法之快速排序详解
  • HTML零基础入门——hello world
  • html中span标签的详细介绍
  • 交换机的背板带宽,交换容量,包转发率区别以前计算公式
  • sxs.exe 的查杀.bat
  • oracle通配符大全
  • windows CE软件下载
  • 顶级白帽黑客必备的十大黑客技术
  • 博客园美化教程总结
  • ISO/IEC技术标准-RFID
  • 《仙剑奇侠传四》完全攻略
  • 网络爸爸 v8.1.9.22 官方
  • 刷QQ空间人气
  • c++学习论坛推荐
  • 聚生网管破解及安装
  • 【江西软件职业技术大学毕业论文】 电影网站的设计与实现
  • 雅虎14条性能优化(经典)
  • dvdscr是什么意思?什么是dvdscr格式?
  • 关于Tomcat有这一篇就够了