ExcelTip.Net留存知识帖 ---【注:附件之前被网盘供应商清空后,现已修复-现已修复-现已修复为本地下载!】
现在位置:首页 > E文精选 > Excel VBA > 如何在VBA中使用钩子(HOOK)以及WINDOWS消息机制简介

如何在VBA中使用钩子(HOOK)以及WINDOWS消息机制简介

作者:绿色风 分类: 时间:2022-08-17 浏览:440
楼主
い卋玑┾宝珼
  要讲钩子(HOOK),就要了解WINDOWS的消息机制,还要初步会进行API编程,因此,我们首先简要的讲讲API和WINDOWS消息机制。

  文中的措辞,特别是API编程,都可以写一本书的,本文知识给予API初学者使用,用最通俗的语言去讲解,如有不当,也请各位指正。API很多东西都是C语言过来的,理解不当也请指正。

 
 一、    API基础知识
  (一)WINDOWS API

  WINDOWS API是WINDOWS自带的一套函数集,可以直接访问操作系统的底层,学API的东西,VBA的等级首先要为中级,也就是对VBA有个系统性的了解后,方适宜介入。这些函数,可以在,http://www.vbgood.com/api.html,等资源里面,进行查询。


  (二)动态链接库(DLL)
  API的很多函数保存在哪?存放在DLL文件中,一旦应用程序要调用的时候,可以引用链接某个DLL文件,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,这样,调用这些函数就非常方便。


    (三)API声明
  VBA中,声明格式如下:
  Declare Function 函数别名 Lib “DLL文件名” Alisa “函数名” (ByVal/ByRef 变量名 As 类型,…) As 输出数据类型
  在模块头部声明一下,就可以在VBA里面使用这些函数了,ByVal/ByRef这个事VBA基础的老概念啦,呵呵,自行复习吧。VBA里面数据类型很多是一个自定义数据结构,这个就要复习Type语句啦,呵呵。另外,有时候这个数据类型是个指针,那VBA貌似不支持指针,其实不然。


  (四)了解指针
  指针是啥?最通俗的话讲,指针就是数据的内存地址。在32位WINDOWS中(帖子最后来得及的话,后面讲讲64位的区别吧,变动不是很大,现在还是蛮多机子是32位的,因此从32位讲),指针/内存地址就是32位长的。那储存这个内存地址的变量,就是指针变量。
由于指针访问的就是数据的内存地址,因此,这数据交换等操作中,速度比通常的通过一般变量名赋值,快得多。
  VBA中,和指针最靠近的概念就是ByRef,就是VBA中默认的参数按内存地址传递,即传递实际参数的地址/指针。简而言之,例如,在调用FUNCTION的时候,用ByRef传递参数,实参和形参指向的是同一个内存地址,形参和实参是一起变化的。如果采用ByVal,形参的改变就和实参没关系了,因为是传值,传递的是数据的副本,也就数据的值。这些都是VBA基础,不多讲了。
  VBA如何获取指针呢,很简单,一个函数——VarPtr,这个函数,其实VBA是隐藏函数。
试看看下面的代码:
  1. Sub t()
  2.     Dim a As Long, za As Long, zb As Long
  3.     a = 100
  4.     za = VarPtr(a)    'za就是个指针变量,存放着变量a代表的100这个数据的内存地址。
  5.     zb = VarPtr(100)    '注意,100被保存在临时变量里面了,虽然它没有变量名,还是有地址的
  6.     Debug.Print za, zb '存放的都是内存地址咯
  7. End Sub
  知道如何取出数据的指针了,那如何采用指针的方式,复制数据呢?这就又要用到另一个API函数了——CopyMemory,它的语法是:  CopyMemory目标的指针,复制源的指针,内存字节长度(看数据源的数据类型了,如果是长整形,长度是4个字节,实在不知道就用lenb函数)。这样,就以内存拷贝的形式,进行了数据复制。


  结合上面所说的,大家看懂以下的程序,就没问题了。
  1. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  2. Sub jh()
  3.     Dim a As Long, b As Long
  4.     a = 1000
  5.     CopyMemory b, a, LenB(a)
  6.     Debug.Print b
  7.     CopyMemory b, 2000&, 4
  8.     Debug.Print b
  9.     a = 3000
  10.     CopyMemory b, ByVal VarPtr(a), LenB(a)
  11.     Debug.Print b
  12.     CopyMemory ByVal VarPtr(b), 4000&, 4
  13.     Debug.Print b
  14.     a = 5000
  15.     CopyMemory ByVal VarPtr(b), a, LenB(a)
  16.     Debug.Print b
  17.     a = 6000
  18.     CopyMemory ByVal VarPtr(b), ByVal VarPtr(a), LenB(a)
  19.     Debug.Print b
  20. End Sub
  (五)字符串   另外,再讲讲字符串。VBA的字符串,其实是一个指向Unicode字符数组的指针,也就是:
  1. Dim str as string
  2. str = “help”
  结果如下图
  
 
  Str其实是个指针变量,指向一个Unicode数组,这个数组内存位置的前面4个字节长的字段,是用来储存字符串的字节长度。另外,这个数组,是以两个字节的空字符串结束的。注意,这个指针是指向字符数组的开始,而不是指向前面那个长度字段。另外,我们还可以用Strptr这个隐藏函数来获取这个字符数组的起始地址看看下面的案例:
  1. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  2. Sub st()
  3.     Dim s As String, a() As Byte, cs As String, cd As Long, cs1 As String

  4.     s = "hello"
  5.     a = s    'a就是字符串的unicode数组去最后双字节空字符串的部分,固定用法记住即可
  6.     cs = String(Len(s), vbNullChar) '返回结果的时候,API函数不会自动生成字符串只会填充字符串,必须要做好目标长度的字符串来缓冲,以免内存溢出。
  7.     CopyMemory ByVal cs, ByVal s, LenB(s)
  8.     cs1 = String(Len(s), vbNullChar)
  9.     CopyMemory ByVal StrPtr(cs1), ByVal StrPtr(s), LenB(s)
  10.     CopyMemory cd, ByVal StrPtr(s) - 4, 4 '字符串数组起始位置-4个字节,就是字符串字节长度的起始地址了,由此可以获得这个字符串的字节长度,和lenb函数结果一样。
  11.     Debug.Print cs, cs1, cd
  12. End Sub
  虽然上面两个方法的效果差不多的,但是区别在于下面一个例子:
  1. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
  2. Sub t2()
  3.     Dim s As String, sb1(1 To 5) As Byte, sb2(1 To 5) As Byte, a() As Byte
  4.     s = "12"         '1的ANSI码是49,UNICODE字节数组是49 00,而2的ANSI码是50,UNICODE字节数组是50 00
  5.     a = s
  6.     CopyMemory sb1(1), ByVal s, LenB(s)
  7.     CopyMemory sb2(1), ByVal StrPtr(s), LenB(s)
  8. End Sub

 
  很明显地能看出,VBA默认储存文本的方式是Unicode字符数组,当需要传送UNICODE文本数组给予API函数时,其实,VBA将强制做了个ANSI数组版本的转换拷贝,然后才将这个转换后ANSI版本的字符数组指针传送给API函数,所以返回的结果是不一样的。其实不单单是传出的过程,传回的时候也是一样的,把先前那个ANSI字符数组回转回原来的UNICODE数组。

  (六)过程/函数的指针
  在VBA中一个过程可以调用另一个过程/函数,一样的,API函数也可以调用我们VBA里面的过程。但是,要在API里面传递过程/函数,也是通过过程/函数的指针实现的。VBA里面怎么办呢,用:AddressOf 过程/函数名,AddressOf将其后面的过程/函数的地址传递给一个 API函数。上面这些都是API要注意的,特别在传递参数的时候。下面讲讲与钩子(HOOK)非常相关的,WINDOWS消息传递机制的概念和WINDOWS的一些基础知识。



2楼
い卋玑┾宝珼
二、WINDOWS一些基础概念

  (一)进程和线程  进程是一个正在运行的WINDOWS应用程序的实例和系统分配给它一组必要的资源(内存等)。有些晦涩吧,我们从程序的运行过程开始说吧,比较清晰易懂。
  当我们运行一个程序是,首先,WINDOWS会为这个程序创建一个进程空间,并且为其分配一些资源。然后将可执行文件按照一定规则加载到该进程空间内,操作系统还会根据可执行文件的“说明”,将相应的动态链接库(DLL)映射进程空间内。接下来,WINDOWS会为该进程空间创建一个主线程,为该主线程分配一些资源,主线程从这个exe文件指定的位置开始执行代码,程序开始运行。这么看,进程就是WINDOWS负责为一个程序构造一个独立的环境,这个环境内的资源可以相互直接访问,不同的进程之间则无法直接打交道。当一个进程创建之后,操作系统会自动为它创建一个主线程,用来执行程序代码,那线程又是什么呢?
  线程则是CPU实际执行任务的环境,负责执行进程空间里的代码,一个进程空间可以有多个线程。每个线程包含一组状态,其中一些用来记录和恢复CPU的执行状态,另一些则是由程序的自行记录的内容,还有一些则是给系统内核使用的。WINDOWS内核会进行线程调度,按照一定规则给每个线程分配CPU时间。一般情况下,同级别的每个线程都会获得一小段很短的时间片,使各个任务看起来像在“同时”运行,直到执行完毕或是采用某种方式暂停或退出为止。
  另外,线程中如果创建了窗口,就叫GUI线程,否则就是工作线程。

  
(二)窗口
  大部分应用程序都有它的图形界面,这个就是它的窗口。窗口里面还有很多文本框和按钮等,这些就是控件。窗口句柄就是一个32的数值,用来系统识别这个窗口。那窗口里面的控件,子窗口啊,也一样,也拥有自己的句柄。
  注意,窗口的查找过程,要先用FINDWINDOW函数(自己翻查API手册吧)查找到顶层父窗口的句柄,然后才能用FINDWINDOWEX查询,这个窗口下属的控件或子窗口的句柄。大家看附件WinExplorer.rar这个工具,就是一个高手用VB做的,用来查找窗口句柄的。我们可以用这个工具,和我们程序里面获得的句柄语句进行比对,校验句柄是否正确之用。

  
(三)WINDOWS消息
  1、啥系WINDOWS消息
  WINDOWS是基于事件驱动的,当事件触发了(例如移动鼠标等),WINDOWS系统产生一个消息进行通讯。消息可以是系统产生,也可以是应用程序产生。例如,当应用程序改变了自己窗口大小的时候,系统也会产生消息。
  消息构成如下:
  Hwnd:接受该消息的窗口句柄
  Message:消息常量标识符,也就是我们通常所说的消息类型
  wParam:32位消息的特定附加信息,通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
  lParam:32位消息的特定附加信息,通常是一个指向内存中数据结构(类似VBA中的TYPE自定义结构)的指针
  time:消息创建时的时间
  pt:消息创建时的鼠标/光标在屏幕坐标系中的位置


  2、这个消息有何用?
  产生的消息,将根据消息的窗口句柄,传给相应的窗口,每个窗口都有一个叫做窗口过程的函数,用来处理发送过来的消息。当窗口过程接收到消息之后,他就会使用消息标识符来决定如何处理消息,如果他不处理,它会将消息传回到执行默认的处理。窗口过程通过调用DefWindowProc来做这个默认处理。窗口过程函数处理完消息后就会把控制权转给操作系统。处理的结果,就是我们鼠标点击等后的效果。


  3、消息的类别
  消息主要有系统消息和应用程序消息。当系统要和应用程序通讯的时候,他就会发出或者投递一个系统消息,它使用这些消息来控制应用程序的操作和为应用程序提供输入或者其他信息。应用程序也可以发动和投递系统定义消息,应用程序一般使用这些消息来控制某些控件的动作。另外,应用程序可以创建消息以供他自己的窗口或者和其他进程的窗口通讯而用。


  4、消息的投递过程
  系统使用两个方法来传递消息给窗口过程:一种是投递到消息队列,还有一种就是投递消息到系统定义的一个内存对象临时存储,并且直接把这个消息发送给窗口过程(这个方法不会进入消息队列)。需要投递到消息队列的消息称为队列化消息。他们主要是用户通过键盘或者鼠标输入。其他直接发送给窗口过程的消息称为非队列化消息。消息队列是啥呢?得从他的工作机制进行讲解。


  5、何为消息队列
     系统维护着一个系统级的消息队列,同时为每个GUI线程(前面线程讲了哈)维护着一个线程级的消息队列。所有线程在最初创建的时候是没有消息队列的。只有当线程首次调用用户用户函数或者图形设备接口函数的时候系统才会为线程创建一个消息队列。所以只有GUI线程有消息队列。
     只要用户移动鼠标,单击鼠标按钮或者敲击键盘的时候,设备驱动程序都会为鼠标或者键盘把输入转换成消息,并且把这个消息放在系统消息队列中。系统一次一个的从系统消息队列中取出消息,分析确定目标窗口,接着把这个消息投递到创建目标窗口的线程消息队列中。线程从他的消息队列中取出消息,让系统发送这个消息给对应的窗口过程函数进行处理。
  一般而言,系统总是把消息投递到队列的尾部,这样可以确保窗体遵循先入先出的顺序接收到输入消息,但是消息是有优先等级的,优先等级高的消息先行处理。


  6、消息传递的大致编程原理
     首先,应用程序通过调用GetMessage函数从线程中的消息队列中取出一个消息,然后使用DispatchMessage函数触发系统把这个消息发送给窗口过程函数进行处理。当消息队列中没有消息的时候,线程可以使用WaitMessage函数来把控制权让给其他线程。这个函数可以把当前线程挂起,直到线程消息队列中有新的消息被放置进来,那么线程才会继续执行。
     非队列化消息会绕过系统消息队列和线程消息队列直接发动给目标窗口过程函数,一般系统发送非队列化消息是为了通知受到事件影响的窗体。例如,当用户激活了一个新的应用程序窗口,系统会发送一系列的消息。非队列化消息也可能是应用程序调用某个系统函数导致的结果。


  7、动手玩玩
  了解了上面的这些基础理论之后,我们就可以进行一下简单的消息发送。玩玩常见的SendMessage吧。
  1. Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

  这个函数主要是向一个或多个窗口发送一条消息,一直等到消息被处理之后才会返回。父窗口和子窗口经常使用这个发送消息的方式互相通讯。不过需要注意的是,如果接收消息的窗口是同一个应用程序的一部分,那么这个窗口的窗口函数就被作为一个子程序马上被调用;如果接收消息的窗口是被另外的线程所创建的,那么系统就切换到相应的线程并且调用相应的窗口函数,这条消息不会被放进目标应用程序队列中。函数的返回值是由接收消息的窗口的窗口函数返回的。
  假设我们要去除EXCEL界面的左上角的图表,他对应的消息是什么呢,通过搜索我们的WINDOWS消息大全的附件,会查到,WM_SETICON这个消息,进行上网搜索这个消息,得到:
  参数: wParam:指定图标的类型。该参数可以为下列值之一:
          ICON_BIG   为窗口设置大图标
          ICON_SMALL  为窗口设置小图标
     lParam:新的大、小图标的句柄。如果该参数为NULL,由wParam参数指定的图标将会移除。
  因此,我们只要把lParam参数设为0即可。整个程序就这么短。如下:
  1. Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
  2. Private Const WM_SETICON = &H80
  3. Private Const ICON_SMALL = 0
  4. Private Const ICON_BIG = 1
  5. Sub test()
  6.     Dim hwnd As Long

  7.     hwnd = Application.hwnd '获取EXCEL窗口的句柄
  8.     SendMessage hwnd, WM_SETICON, ICON_BIG, 0&
  9.     SendMessage hwnd, WM_SETICON, ICON_SMALL, 0&
  10. End Sub


WINDOWS消息大全.zip



    讲完了消息传递机制,终于可以开始讲钩子(HOOK)




3楼
い卋玑┾宝珼
三、钩子HOOK

  (一)什么是钩子(hook)
  钩子(hook)是一种特殊的消息处理机制,可以监视系统或进程中的各种事件消息,截获发往某窗口的消息,然后进行处理。主要功能是监视,然后处理消息。说白了,就是一个Windows消息的拦截机制。
  钩子的种类很多,后面我们再详细讲,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘消息等。
  钩子还可以分为线程钩子和系统钩子, 线程钩子监视指定线程的事件消息, 系统钩子监视系统中的所有线程的事件消息。


  (二)工作原理
  在使用钩子前,我们先理解钩子的工作原理。当创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去(系统或特定线程中,是允许同时存在多个钩子的,这样就形成了一个链)。新的钩子将加到老的前面。当一个事件发生时,如果安装的是一个线程钩子,钩子相关的函数将被调用。如果是一个系统钩子,系统就必须把钩子相关的函数插入到其它进程的地址空间,要做到这一点要求该函数必须在一个动态链接库中。
     需要注意:
  (1) 如果对于同一事件(如鼠标消息)既安装了线程钩子又安装了系统钩子,那么系统会自动先调用线程钩子,然后调用系统钩子。
  (2) 对同一事件消息可安装多个钩子处理过程,这些钩子处理过程形成了钩子链。当前钩子处理结束后应把钩子信息传递给下一个钩子的函数。而且最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
  (3) 钩子特别是系统钩子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装钩子,在使用完毕后要及时卸载。


  (三)制作钩子的标准流程和相关API函数说明
  1、制作钩子挂钩的函数  钩子函数指钩子在拦截了消息后,进行对应消息处理的函数,也可以通过返回其值TRUE直接抛弃消息,其格式为:
    函数名(nCodeas long, wParam as long,lParam as Any)
   参数说明:
    nCode:包含所拦截的消息本身的内含信息。(附件钩子函数有详细说明)
    wParam:消息标识,用于判断该消息是那种消息,如WM_MOUSEMOVE,WM_NCMOUSEMOVE
    lParam:包含所钩消息的信息指针,比如鼠标位置、状态,键盘按键等。


   2、如何创建钩子
  需要API SetWindowsHookEx函数:
  1. Public Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long

  这函数的返回值是这个钩子的句柄
  参数说明:
  idHook:钩子的拦截消息类型
  lpfn:拦截消息的函数指针,若第四个参数dwThreadId为0或者指向了一个其他进程创建的线程之标识符,则参数lpfn必须指向一个动态链接中的函数。反之,指向的是本进程内的线程,参数lpfn可以指向一个与当前进程相关的代码中定义的挂钩处理函数,VBA中表示为ADDRESSOF 函数名
  hMod:钩子函数所在的动态链接的句柄。若参数dwThreadId指示的线程由当前进程创建,并且相应的挂钩处理函数定义于当前进程相关的代码中,则参数hMod必须被设置为0&。
  dwThreadId:钩子所监视的线程的线程标识,可通过GetCurrentThreadId()获得线程号。对于系统钩子,该参数为0&。


   3、创建钩子后,通过钩子函数处理消息后,需要将信息传递给下一个钩子:
  1. Public Declare Function CallNextHookEx Lib "user32.dll" (ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, lparam As Any) As Long

  hHook:钩子句柄。
  nCode、wParam和lParam 是钩子函数对应的参数。


   4、卸载钩子函数
  当不再使用钩子时,必须及时卸载。
  1. Private Declare Function UnhookWindowsHookEx Lib "user32.dll" (ByVal hHook As Long) As Long

  参数hHook就是钩子的句柄。


示例HOOK.zip


 (四)例子
  我们举一个,网上问得多的一个案例,代码也是某个高手写的,我拿出来做案例。
  例如,当我们在编辑单元格的时候,在状态栏显示,“正在编辑单元格…”,有点像我们的QQ窗口,当聊天时,对方处于输入状态,我们的QQ窗口提示的“正在输入中…”
  分析:当单元格处于编辑状态,也就是单元格获得了键盘焦点,查阅附录,钩子的类型,我们应该选择WH_CBT类型,再查阅附件,“钩子函数”,我们发现,ncode参数要判断的是HCBT_SETFOCUS这个类型,继续查找,将发现wParam参数,是获得键盘焦点的窗口句柄。那我们用GetClassName函数,通过句柄查找窗口的类型名称,用附件的WINEXPLORE可以简单的知道,EXCEL处于单元格的窗口类名称是“EXCEL6”。当然,编辑单元格,也可以在编辑框进行,这个编辑框的类名称就是“EXCEL<”。如图:

WinExplorer.rar








  最后,书写代码:
  1. Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
  2. Public Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
  3. Public Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
  4. Public Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, lparam As Any) As Long
  5. Public Declare Function GetCurrentThreadId Lib "kernel32" () As Long

  6. Public Const HCBT_SETFOCUS = 9
  7. Public Const WH_CBT = 5
  8. Public IHook As Long
  9. Public IThreadId As Long
  10. Public ClassName As String
  11. '设置钩子
  12. Public Sub EnableHook()
  13.     If IHook = 0 Then
  14.         IThreadId = GetCurrentThreadId    '取得当前线程的ID
  15.         IHook = SetWindowsHookEx(WH_CBT, AddressOf HookProc, 0&, IThreadId)
  16.     End If
  17. End Sub
  18. '取消钩子
  19. Public Sub FreeHook()
  20.     If IHook <> 0 Then
  21.         Call UnhookWindowsHookEx(IHook)
  22.         IHook = 0
  23.     End If
  24. End Sub
  25. '钩子函数
  26. Public Function HookProc(ByVal nCode As Long, ByVal wParam As Long, ByVal lparam As Long) As Long
  27.     If nCode < 0 Then
  28.         HookProc = CallNextHookEx(IHook, nCode, wParam, lparam)
  29.         Exit Function
  30.     End If
  31.     If nCode = HCBT_SETFOCUS Then
  32.         ClassName = String(255, Chr(0))
  33. '获取窗口类名称
  34.         GetClassName wParam, ClassName, 255
  35.         ClassName = Left(ClassName, InStr(ClassName, vbNullChar) - 1)
  36.         If ClassName = "EXCEL<" Or ClassName = "EXCEL6" Then
  37.             Application.StatusBar = "正编辑单元格……" '变更状态栏文字
  38.         Else
  39.             Application.StatusBar = False '恢复状态栏
  40.         End If
  41.     End If
  42.     HookProc = CallNextHookEx(IHook, nCode, wParam, lparam) '传递给下一个钩子
  43. End Function


      附:钩子拦截消息类型(相关的结构,可以在MSDN查询,也可以查看附件HOOK常用数据结构.zip,不过是C语言的,使用时候翻译过来就是)

  1WH_CALLWNDPROC(常量为4)和WH_CALLWNDPROCRET(常量为12钩子
  WH_CALLWNDPROCWH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程。
  WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。
  CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。



  2WH_CBT 钩子(常量为5
  在以下事件之前,系统都会调用WH_CBTHook子程,这些事件包括:
  1)激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
  2)完成系统指令;
  3)来自系统消息队列中的移动鼠标,键盘事件;
  4)设置输入焦点事件;
  5)同步系统消息队列事件。
  Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。



  3WH_DEBUG 钩子(常量为9
  在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_DEBUG Hook子程。你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。



  4WH_FOREGROUNDIDLE 钩子(常量为11
  当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLEHook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。



  5WH_GETMESSAGE 钩子(常量为3
  应用程序使用WH_GETMESSAGEHook来监视从GetMessage orPeekMessage函数返回的消息。你可以使用WH_GETMESSAGEHook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。



  6WH_JOURNALPLAYBACK 钩子(常量为1
  WH_JOURNALPLAYBACKHook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORDHook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACKHook已经安装,正常的鼠标和键盘事件就是无效的。
  WH_JOURNALPLAYBACKHook是全局Hook,它不能象线程特定Hook一样使用。
  WH_JOURNALPLAYBACKHook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。
  WH_JOURNALPLAYBACKsystem-wide local hooks,它们不会被注射到任何行程位址空间。



  7WH_JOURNALRECORD 钩子(常量为0
  WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACKHook来回放。
  WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。
  WH_JOURNALRECORDsystem-wide local hooks,它们不会被注射到任何行程位址空间。



  8WH_KEYBOARD 钩子(常量为2
  在应用程序中,WH_KEYBOARDHook用来监视WM_KEYDOWN andWM_KEYUP消息,这些消息通过GetMessage orPeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。



  9WH_KEYBOARD_LL 钩子(常量为13
  WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。



  10WH_MOUSE 钩子(常量为7
  WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。

  11WH_MOUSE_LL 钩子(常量为14
  WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。



  12WH_MSGFILTER(常量为-1 WH_SYSMSGFILTER 钩子(常量为6
  WH_MSGFILTER WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTERHook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTERHook监视所有应用程序消息。
  WH_MSGFILTER WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。
  通过调用CallMsgFilterfunction可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。



  13WH_SHELL 钩子(常量为10
  外壳应用程序可以使用WH_SHELLHook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。
  WH_SHELL 共有5钟情况:
  1)只要有个top-levelunowned 窗口被产生、起作用、或是被摧毁;
  2)当Taskbar需要重画某个按钮;
  3)当系统需要显示关于Taskbar的一个程序的最小化形式;
  4)当目前的键盘布局状态改变;
  5)当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。
  按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfofunction注册它自己。

钩子函数.zip








4楼
い卋玑┾宝珼
新添加示例:拦截保护工作表的对话框


拦截.zip


如果不使用拦截,则会弹出以下对话框

 

有了这个示例,我们就可以随心所欲的拦截对话框了


 


参考代码如下:
  1. Option Explicit
  2. Private Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, ByVal hmod As Long, ByVal dwThreadId As Long) As Long
  3. Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
  4. Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, lParam As Any) As Long
  5. Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
  6. Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
  7. Private Declare Function GetDlgItemText Lib "user32" Alias "GetDlgItemTextA" (ByVal hDlg As Long, ByVal nIDDlgItem As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long
  8. Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

  9. Public Const HCBT_ACTIVATE = 5
  10. Public Const WH_CBT = 5
  11. Public Hook As Long

  12. Sub SetHook()
  13.     Dim ThreadId As Long
  14.     If Hook = 0 Then
  15.         ThreadId = GetCurrentThreadId '获取当前线程的ID
  16.         Hook = SetWindowsHookEx(WH_CBT, AddressOf KillPrt, 0&, ThreadId) '上钩子咯
  17.     End If
  18. End Sub

  19. Function KillPrt(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  20.     Dim WinText As String
  21.     If nCode = HCBT_ACTIVATE Then '如果是激活一个窗口的消息,开始拦截
  22.         WinText = String(255, vbNullChar) '做识别窗体标题的缓存文本
  23.         GetWindowText wParam, WinText, 255 '获取窗口标题文本
  24.         WinText = Left(WinText, InStr(WinText, vbNullChar) - 1) '去除多余的/0
  25.         If WinText = "Microsoft Excel" Or WinText = "Microsoft Office Excel" Then '如果是Microsoft Excel抬头的对话框,再进一步识别其对话框的消息
  26.             Dim Stxt As String
  27.             Stxt = String(1000, vbNullChar) '设置取对话框的消息的缓冲文本
  28.             GetDlgItemText wParam, 4001&, Stxt, 1000 '通过对话框的消息标签控件的ID,获取其文本
  29.             If InStr(Stxt, "保护单元格或图表") > 0 Then PostMessage wParam, &H10&, 0&, 0& '确定如果是保护工作表引起的对话框,强制关闭它
  30.         End If
  31.     End If
  32.     KillPrt = CallNextHookEx(Hook, nCode, wParam, lParam) '传递给下一个钩子
  33. End Function

  34. Sub ReleaseHook()
  35.     If Hook <> 0 Then
  36.         Call UnhookWindowsHookEx(Hook) '释放钩子
  37.         Hook = 0
  38.     End If
  39. End Sub

  40. Sub CallMsg()
  41.     MsgBox "我是干扰对话框,抬头也叫Microsoft Excel!" '标题也叫Microsoft Excel的干扰对话框。
  42. End Sub
文章中涉及到的内容所有的附件

以及参考文档见此楼

钩子函数.zip
WinExplorer.rar
示例HOOK.zip
一篇外文文献.zip
帖子.zip
HOOK常用数据结构.zip
WINDOWS消息大全.zip
5楼
lnt1231
占楼围观,有能力的时候再学
6楼
DJ_Soo
谢谢分享!有些还是看不懂呀,得慢慢消化.
有个问题请教一下:
关于三楼的代码,可以用什么方法获取编辑框的内容吗?
个人感觉如果能获取编辑框内容的话,可以搞出很多有用的东西.(在编辑状态下获取)
7楼
い卋玑┾宝珼
哪里的编辑框是Excel单 元格的编辑框还是?
8楼
DJ_Soo
这个都可以的呀,就是正在编辑的那个单元格没回车之前的内容.
也就是当前激活的那个编辑框里面的内容.
9楼
BianChengNan
必须精华啊,真心学习了
10楼
lslly
留记号,以后学习了
11楼
纵鹤擒龙水中月
有能力的时候再学
12楼
avel
这个帖子精品。。 要好好学习的
13楼
水星钓鱼
原来这里还有个这么牛的帖子啊。
14楼
老糊涂
收藏了
15楼
sunxm
谢谢分享!

免责声明

有感于原ExcelTip.Net留存知识的价值及部分知识具有的时间限定性因素, 经与ExcelTip.Net站长Apolloh商议并征得其同意, 现将原属ExcelTip.Net的知识帖采集资料于本站点进行展示, 供有需要的人士查询使用,也慰缅曾经的论坛时代。 所示各个帖子的原作者如对版权有异议, 可与本人沟通提出,或于本站点留言,我们会尽快处理。 在此,感谢ExcelTip.Net站长Apolloh的支持,感谢本站点所有人**绿色风(QQ:79664738)**的支持与奉献,特此鸣谢!
------本人网名**KevinChengCW(QQ:1210618015)**原ExcelTip.Net总版主之一

评论列表
sitemap