想做一个工具,当鼠标移动时即时查看鼠标当前的坐标,现在终于解决,过程记录如下:
首先,为了要捕获鼠标移动消息并获取其坐标,我首先想到的是钩子,于是采用SetWindowsHookEx函数,利用本函数给整个系统加载钩子能实现本功能,不过对系统消耗较大,用法可以参见SetWindowsHookEx。
SetWindowsHookEx使用的关键是以下几点:
1、如果只是钩特定的某个线程,可以直接在工具代码中调用SetWindowsHookEx函数,最后一个参数填写要钩的线程id,但是要钩整个系统时,最后一个参数必须填0,并且SetWindowsHookEx和钩子回调函数都必须在dll中实现,第三个参数填写dll的实例句柄。测试发现:如果建立全局钩子,第四个参数填0,但是实现就在本进程中,结果是:钩子SetHook正确,返回正确值;如果hook的是鼠标,那么当鼠标一直在本进程窗口上移动时,回调函数调用正确,证明钩子已经在系统中正确安装并且起作用,如果这些操作后就卸载钩子UnhookWindowsHookEx,返回值正确;但是如果安装后,鼠标移出本进程窗口,不管以后是否鼠标再回到进程窗口内,回调函数中的指令都不在执行,证明钩子已经坏掉。此时调用UnhookWindowsHookEx函数,也会发现返回失败。错误码1404(1404 用户清除和删除失败 ),进一步说明钩子已经被破坏。原因是鼠标移出窗口进入别的进程,从而要求别的进程加载本钩子模块,但是这是访问别的进程的地址空间,所以失败。可能windows在检测到这个错误时就会自动清理掉这个失败的钩子。所以钩子回调函数失效并且UnhookWindowsHookEx返回失败。
2、运行机制:在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。当进程在载入DLL时,操作系统自动把DLL地址映射到该进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。
在本问题环境下我们需要想在多个进程中共享数据,在Win32环境下就必须进行必要的设置。在访问同一个Dll的各进程 之间共享存储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些 变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。方法如下:
#pragma data_seg预处理指令用于设置共享数据段。例如:
#pragma data_seg("SharedDataName")
HHOOK hHook = NULL;
//其他共享数据
#pragma data_seg()
在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到和共享。再加上一条指令#pragma comment(linker,"/section:SharedDataName,rws"),那么这个数据节中的数据可以在所有DLL的实例之间共 享。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。
3、用SetWindowsHookEx方法建立全局钩子时,有一些显而易见的缺点:首先值得我们注意的是,Windows钩子将会降低整个系统的性能,因为它额外增加了系统在消息处理方面的时间;其次,只有当目标进程准备接受某种消息时,钩子所在的DLL才会被系统映射到该进程的地址空间中,钩子才能真正开始发挥作用,因此如果我们要对某些进程的整个生命周期内的API调用情况进行监控,这种方法显然会遗漏某些API的调用 。
以上是用hook实现检测鼠标方法,显然hook功能不仅仅这些。不过可能比较消耗系统资源。另外我还发现一种笔记简单的方法:利用SetCapture捕获光标(函数请参见SetCapture)。该方法很简单并且有效,只需要在希望捕获前调用SetCapture,不要时再调用ReleaseCapture即可。不过有以下几点需要注意:
1、当进程的窗口调用的SetCapture以后,他将锁定所有鼠标输入,这可能导致窗口的其他按钮包括关闭都无法点击,所以最好在消息循环中处理某个事件来管理Capture的逻辑,比如鼠标中键,键盘某个快捷键等等。这在调用ReleaseCapture释放光标以后都会正常。
2、对光标的捕获如果是超出了当前进程的窗口,是需要先在当前进程窗口下按住鼠标左键在移出窗口才有效的!这也是许多spy的处理原理。当时我没发现这一点,做了很久总是不能实现,很是不解,郁闷n久,白白浪费了很多脑细胞~!特此着重提醒各位过往客官!
3、用SetCapture注意的问题:设定当前窗口捕获光标后,如果焦点转移,比如激活了别的窗口,此时本窗口的光标捕获也不存在了。所以如果有捕获状态的显示的话,一定要记得处理CaptureChanged消息!
好了,鼠标捕获就研究到这里了。声明:在写作的过程中,为了在某些点上表达得更清晰,参考了网上一些朋友的博客,有些引用原话,没有列举出处,请见谅。有不对的地方,欢迎指正~