1. 新建一个 module ,先写一下 API 的声明:
Declare Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" ( ByVal idHook As HookType, ByVal lpfn As HOOKPROC, ByVal hmod As Integer , ByVal dwThreadId As Integer ) As Integer
Declare Function UnhookWindowsHookEx Lib "user32" ( ByVal hHook As Integer ) As Integer
Declare Function CallNextHookEx Lib "user32" ( ByVal hHook As Integer , ByVal ncode As Integer , ByVal wParam As Integer , ByVal lParam As Integer ) As Integer
和上面vb6里的声明比一下,你发现了什么?是不是数据类型发生了变化? integer代替了long。这个好理解,因为vb.net中integer定义为32位(4字节)的整数,值的范围是-2 31 到2 31 (首位是符号位),这与vb6中long的定义是一致的,因此,我们必须做一下这样的转换。
等等,还有一个变化,就是 SetWindowsHookEx 的参数 lpfn 的类型变成了 HOOKPROC ,那……那是什么意思。噢, wait, 我再补一句声明先:
Public Delegate Function HOOKPROC( ByVal nCode As Integer , ByVal wParam As Integer , ByVal lParam As Integer ) As Integer
看出来什么了吗? HOOKPROC 其实就是一个函数声明,但是前面有一个 Delegate (委托)是什么意思?为什么在 vb.net 中不能用 integer 表示 lpfn 的类型了呢?我们回头看一下 vb6 中调用 API 的句子:
hnextHookproc = SetWindowsHookEx(WH_KEYBOARD, AddressOf _
MyKBHFunc, App.hInstance, 0)
Lpfn 在这里被表示为 AddressOf MyKBHFunc ,即说明 Hook 发生作用时,调用的子程是 MyKBHFunc 。也就是说 lpfn 表示的是函数或过程的地址。在 vb6 中用 long 类型就可以记录下函数或过程的地址。
在 vb.net 中,有了一点小变化喽, ** AddressOf ** 运算符创建的是一个指向指定的子程的子程委托。当指定的子程是一个实例方法时,子程委托同时引用实例和方法,以便当调用该子程委托时,调用指定实例的指定方法。 AddressOf 运算符可以用作委托构造函数的操作数,或可以用在编译器能够确定委托类型的上下文中。
所以,正是由于 Addressof 创建的不再只是简单的函数指针了,而是子程委托!打住先,什么是委托?( ^?^ )
解释一下:事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如鼠标单击)引起的,也可能是由某些其他的程序逻辑触发的。引发(触发)事件的对象叫做 事件发送方 。捕获事件并对其作出响应的对象叫做 事件接收方 。在事件通讯中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机制)。 .NET 框架定义了一个特殊的类型( Delegate ),该类型提供函数指针的功能。看,这里提到了 .net 框架,所以 C# 等 vs.net 中的语言都可以有这个类型喽。
委托就是可用于调用其他对象方法的对象。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。这样,委托就等效于一个类型安全函数指针或一个回调。因为它们与其他编程语言中所使用的函数指针相似。但不同于函数指针, Visual Basic.NET 委托是基于 ** System.Delegate ** 类的引用类型,它可以引用共享方法 — 无需特定即可调用的方法 — 和实例方法。(具体内容请自已去查阅一下 MSDN 或等我的后续文章再说明)
回过头来总结一下,也就是说, Addressof 创建的是 DelegateType (委托类型)。而不是简单的子程指针了,所以它的表示法就不是地址类型的 long 了,而是与调用的子程相一致的委托类型表示形式。因此,我定义了一个与 MyKBHFunc 声明同形的委托函数 HOOKPROC 来表示 lpfn 的类型。
(呼,一头汗,还不知道说清楚了没有。希望是说清楚了……)
继续,我又接着声明了一个 API :
Declare Function GetCurrentThreadId Lib "kernel32" Alias "GetCurrentThreadId" () As Integer
函数说明:本函数是用于 获取当前线程一个唯一的线程标识符。返回值:当前的线程标识符。这个有什么用,一会再说,反正是个简单的问题,不如卖个关子,哈哈……(不要砸我)
2. 定义的常量是:
Public hnextHookproc As Long
Public Const WH_KEYBOARD = 2 ‘ 这个是表明 Hook 的种类是键盘 Hook
Public Const PM_KEY_SPACE = &H20 ‘ 空格键
或者,实际上也是,我在程序中对上面的第二句写法改变了一下,也没什么了,就是多交待一点东西给朋友们嘛:
Public Enum HookType
WH_KEYBOARD = 2
End Enum
定义成了一个枚举。其实 Hook 的种类真的很多,比如有: WH_CALLWNDPROC 、 WH_CALLWNDPROCRET 、 WH_CBT 、 WH_DEBUG 、 WH_GETMESSAGE 等等。所以你不妨写一个枚举,以达到一劳永逸的目的。
3. 代码段
Module Module1
Public frm1 As New Form1() ‘ 这个的作用,最后再说
Declare Function GetCurrentThreadId Lib "kernel32" Alias "GetCurrentThreadId" () As Integer
Declare Function SetWindowsHookEx Lib "user32" Alias _
"SetWindowsHookExA" ( ByVal idHook As Integer , ByVal lpfn As HOOKPROC, _
ByVal hmod As Integer , ByVal dwThreadId As Integer ) As Integer
Declare Function UnhookWindowsHookEx Lib "user32" _
( ByVal hHook As Integer ) As Integer
Declare Function CallNextHookEx Lib "user32" ( ByVal hHook As Integer , _
ByVal ncode As Integer , ByVal wParam As Integer , ByVal lParam As Integer ) As Integer
Public Delegate Function HOOKPROC( ByVal nCode As Integer , ByVal wParam As Integer , ByVal lParam As Integer ) As Integer
Public hnexthookproc As Integer
Public Const PM_KEY_SPACE = &H20
Public Enum HookType
WH_KEYBOARD = 2
End Enum
Public Sub UnHook() ‘ 解 Hook
If hnexthookproc <> 0 Then
UnhookWindowsHookEx(hnexthookproc)
hnexthookproc = 0
End If
End Sub
Public Function SetHook() ‘设置 Hook
If hnexthookproc <> 0 Then
Exit Function
End If
hnexthookproc = SetWindowsHookEx(HookType.WH_KEYBOARD, AddressOf MyKeyboardProc, 0, GetCurrentThreadId())
我把第三个参数设为 0 (即 NULL ),表示的是此 Hook 的代码在此进程中。第四个参数用了一个 API 去取 安装 Hook 子程相关联的线程的标识符。 (参见前面的 API 声明)
End Function
Public Function MyKeyboardProc( ByVal nCode As Integer , ByVal wParam As Integer , ByVal lParam As Integer ) As Integer
MyKeyboardProc = 0
If nCode < 0 Then
MyKeyboardProc = CallNextHookEx(hnexthookproc, nCode, wParam, lParam)
Exit Function
End If
If wParam = PM_KEY_SPACE Then
MyKeyboardProc = 1
‘ 写入你自己的代码
frm1.textbox1.text=”HOOK 成功! ”
End If
End Function
Sub main()
Application.Run(frm1)
End Sub
End Module
同时请在:
解决方案管理器 - 〉 windowsapplication1.sln - 〉右点鼠标 - 〉属性 - 〉通用属性 -> 常规 -> 启动对象 - 〉改为 Module1
4. 在 Form1 中的代码:
Private Sub Form1_Load( ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase .Load
Call SetHook()
End Sub
‘vb.net 中没有 form_unload 事件了,而是用 closing
Private Sub Form1_Closing( ByVal sender As Object , ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase .Closing
Call UnHook()
End Sub
最后简单说明一下,为什么我在 module1 里用了 Public frm1 As New Form1() 这句话,及启动对象 - 〉改为 Module1 的作法。这是由于 vb.net 已经是 OO 的了,如果你是 CSDN 上 vb.net 版的常客,你就会很熟悉这个问题,我们已经讨论过 N 次了。我也回过不知多少贴子来说明这一问题。由于和本文主题与篇幅所限,您要是对这个问题不明白,请先看一下:
http://www.csdn.net/expert/topic/965/965919.xml
获得一些概念。我会在后续的文章中进行更为详细和系统的介绍(其实它也是我最想先写的问题之一)。
结束语:关于 API <SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Ti