【C#】解决MouseHook捕获鼠标动作,在有些电脑上SetWindowsHookEx失败返回0的问题
原文:【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 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()
{
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#钩子函数放在线程里钩不上的解决办法
【C#】解决MouseHook捕获鼠标动作,在有些电脑上SetWindowsHookEx失败返回0的问题的更多相关文章
- (已解决)Arduino mega2560 R3插在电脑上没有反应
OK,话不多说.网上找了一些资料,感觉都说的不够清晰.自己琢磨了下,有了一个简单粗暴的方法. 步骤1:插上Arduino mega2560板子.没有反应. 步骤2:我的电脑-管理-设备管 ...
- WPF,强制捕获鼠标事件,鼠标移出控件外依然可以执行强制捕获的鼠标事件
在WPF中,只有鼠标位置在某个控件上的时候才会触发该控件的鼠标事件.例如,有两个控件都注册了MouseDown和MouseUp事件,在控件1上按下鼠标,不要放开,移动到控件2上再放开.在这个过程中,控 ...
- C#捕获鼠标消息
在C#中怎样禁用鼠标按键,我们可以通过ImessageFilter接口下的PreFilterMessage方法.Application类的AddMessageFilter方法,RemoveMessag ...
- [UE4]工程设置:自动捕获鼠标、通过代码设置鼠标显示隐藏、输入模式、编译时自动保存
一.在4.20版本中运行游戏,在没有进行任何设置的情况下,游戏不会自动捕获鼠标,游戏不会接受输入,需要手动点一下游戏界面才行.如果要跟老版本一样运行游戏自动捕获鼠标,需要进行设置 二.也可以通过代码的 ...
- C#使用全局钩子(hook),SetWindowsHookEx返回0、不回调的解决
http://www.csharpwin.com/csharpspace/3766r5747.shtml 在.net 2005平台下 在使用全局hook时,总是遇见SetWindowsHookEx的返 ...
- 解决IIS7.0服务和用户上传的文件分别部署在不同的电脑上时,解决权限的问题
为解决IIS服务和用户上传的文件分别部署在不同的电脑上时,解决权限的问题. 定义: A:iis服务器 B:文件服务器 步骤: 1.在B上创建一个用户[uploaduser](并设置密码) 2.给B上的 ...
- JavaScript中的ParseInt("08")和“09”返回0的原因分析及解决办法
今天在程序中出现一个bugger ,调试了好久,最后才发现,原来是这个问题. 做了一个实验: alert(parseInt("01")),当这个里面的值为01====>07时 ...
- Struts2文件上传方式与上传失败解决方式
首先将几个对象弄出来第一个 上传页面第二个 上传action第三个 startut2配置文件 我的文字描述不是很好,但是终归是自己写出来的,后来我在网上看到一篇关于文件上传描述的非常清楚的文章, 链接 ...
- iOS相关,过年回来电脑上的证书都失效了,解决方法。
今天发了个问题,就是关于电脑上的证书都失效的问题,就这个问题的解决方法如下:https://segmentfault.com/q/1010000004433963 1,按照链接下载,https://d ...
随机推荐
- Android 虚拟机学习总结Dalvik虚拟机介绍
1.Dalvik虚拟机与Java虚拟机的最显著差别是它们分别具有不同的类文件格式以及指令集.Dalvik虚拟机使用的是dex(Dalvik Executable)格式的类文件,而Java虚拟机使用的是 ...
- Android SqlDelight具体解释和Demo样例
一.简单介绍 SQLDelight 和 SqlBrite 是 Square 公司推出的一个 Android 平台数据库解决方式. 在了解这个两个东西前,必须先得有Andorid的Sqlite的知识(S ...
- 批量解决 word/wps 中公式和文字不对齐的问题
完美解决Word或wps中中公式和文字对不齐的问题 在 word 的各个版本中,当公式和字符同时出现时,尤其是发生公式的拷贝粘贴时,公式往往会出现上飘或下移的情况,这里给出一个简单易行的解决方案: 全 ...
- style.height、offsetHeight、clientHeight、scrollHeight的差别
style.height 包含元素的滚动栏,不包含边框 clientHeight 不包含元素的滚动栏和边框 offsetHeight 包含元素的滚动栏和边框 scrollHeight offsetHe ...
- 设置statusBar状态栏颜色
设置statusBar的[前景部分] 简单来说,就是设置显示电池电量.时间.网络部分标示的颜色, 这里只能设置两种颜色: 默认的黑色(UIStatusBarStyleDefault) 白色(UISta ...
- Spring学习笔记之六(数据源的配置)
1.前言 上一篇博客分析了,Spring中实现AOP的两种动态代理的机制,以下这篇博客.来解说一下Spring中的数据源的配置. 2.DAO支持的模板类 Spring提供了非常多关于Dao支持的模板 ...
- 2014-07-20 体验到的不是北漂easy
北京首出租天,房子很潮,这房子我住了一个多月,我希望我真的不会活得很长,世界上只有一个真正的租房,只有明确的家是最好的. 550每月,不包括水电费.我不知道该怎么形容,房间里闪耀的太阳.一个窗口,一扇 ...
- request.getSession().getServletContext().getRealPath()的一些坑
今天是学校机房的服务器上对之前的一个网站升级时发现了一个bug,我自己的机器上用的tomcat8,机房上是tomcat7,结果一运行就开始报找不到文件,最后发现是文件分隔符的问题 原来在代码中涉及到路 ...
- Swift API设计原则
注: 本文摘自 Swift API设计指南 一.基本原则 通俗易懂的API是设计者最重要的目标.实体.变量.函数等都具有一次申明.重复使用的性质,所以一个好的API设计,应该能够使用少量的解读和示例就 ...
- RedisMQ
RedisMQ 本次和大家分享的是RedisMQ队列的用法,前两篇文章队列工厂之(MSMQ)和队列工厂之RabbitMQ分别简单介绍对应队列环境的搭建和常用方法的使用,加上本篇分享的RedisMQ那么 ...