利用底层键盘钩子屏蔽任意按键

    很多人都知道,如果想在系统范围内屏蔽键盘上的任意按键需要使用全局键盘钩子,然而像win键这样“倔强”的按键又不是普通的键盘钩子就能搞定的。这里我提供一种利用底层键盘钩子屏蔽任意按键(包括win键)的方法,并且作成了.dll动态链接库,方便以后使用。钩子,是一种相对复杂一点的技术,通常用来监视系统中某一类型的事件,这些事件可以与某一线程相关(线程钩子),也可以是系统中的所有线程(全局钩子)。关于钩子的理论,我不想说太多,也无法说太多,因为那不是三言两语就能说清楚的。

     本文的重点在于底层键盘钩子的应用,前些天CSDN的VB版有人问如何实现屏蔽win键,说实话,这东西用VB也是可以做到的,只不过全局钩子的钩子函数必须写在标准dll中,而VB只能通过变通的方法做出标准dll,稍微有点麻烦,所以我索性用VC写了一个dll,这样VC、VB或Delphi等等都可以调用,而且我也留出了足够的接口,稍后就会看到。

     有一点必须得声明一下,底层键盘钩子有一个半致命的缺点,就是只能在NT及其以上系统中使用,不过好在现在用2000、XP、2003的人绝对不在少数,将来用LongHorn的人估计也少不了,所以这点倒是不用太担心。  :)

     好了,闲话少说,源代码在此:


 

     DLL头文件(在VC中使用这个DLL中的函数时,需要包含这个头文件,就像使用API要包含windows.h一样):

 

/********************************************************************/

/* 文件名: MaskKey.h                                                */

/*                                                                  */

/* 功能: 标准 DLL 导出函数头文件, 在使用该DLL的程序中包含此文件     */

/*                                                                  */

/* 作者: 卢培培 (goodname008)             时间: 2004.8.21           */

/*                                                                  */

/* BLOG: http://csdn.blog.net/goodname008                           */

/********************************************************************/

 

DECLSPEC_IMPORT

BOOL

WINAPI

StartMaskKey(

     LPDWORD lpdwVirtualKey,

     int nLength,

     BOOL bDisableKeyboard = FALSE

     );

 

DECLSPEC_IMPORT

BOOL

WINAPI

StopMaskKey();
 

 

     DLL主文件:

 

/********************************************************************/

/* 文件名: MaskKey.cpp                                              */

/*                                                                  */

/* 功能: 标准 DLL ---- 利用底层键盘钩子实现屏蔽键盘任意按键         */

/*                                                                  */

/* 作者: 卢培培 (goodname008)             时间: 2004.8.21           */

/*                                                                  */

/* BLOG: http://csdn.blog.net/goodname008                           */

/********************************************************************/

 

2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286  



// 导出函数列表

// StartMaskKey

// StopMaskKey

 

#define _WIN32_WINNT  0x0500                   // 设置系统版本, 确保可以使用底层键盘钩子

 

#include "windows.h"

 

// 全局变量

LPDWORD       g_lpdwVirtualKey = NULL;         // Keycode 数组的指针

int           g_nLength = 0;                   // Keycode 数组的大小

BOOL          g_bDisableKeyboard = FALSE;      // 是否屏蔽整个键盘

HINSTANCE     g_hInstance = NULL;              // 模块实例句柄

HHOOK         g_hHook = NULL;                  // 钩子句柄

 

// DLL 入口函数

BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)

{

     // 保存模块实例句柄

     g_hInstance = (HINSTANCE)hModule;

     

     // 在进程结束或线程结束时卸载钩子

     switch (ul_reason_for_call)

     {

     case DLL_PROCESS_ATTACH:

         break;

     case DLL_THREAD_ATTACH:

         break;

     case DLL_PROCESS_DETACH:

     case DLL_THREAD_DETACH:

         delete g_lpdwVirtualKey;

         if (g_hHook != NULL) UnhookWindowsHookEx(g_hHook);

         break;

     }

    return TRUE;

}

 

// 底层键盘钩子函数

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)

{

     // 禁用键盘的某个按键, 如果 g_bDisableKeyboard 为 TRUE 则禁用整个键盘

     if (nCode == HC_ACTION)

     {

         if (g_bDisableKeyboard) return TRUE;

         KBDLLHOOKSTRUCT* pStruct = (KBDLLHOOKSTRUCT*)lParam;

         LPDWORD tmpVirtualKey = g_lpdwVirtualKey;

         for (int i = 0; i < g_nLength; i++)

         {

              if (pStruct->vkCode == *tmpVirtualKey++)

                   return TRUE;

         }

         

     }

     

     // 传给系统中的下一个钩子

     return CallNextHookEx(g_hHook, nCode, wParam, lParam);

}

 

/********************************************************************/

/* 开始屏蔽键盘按键                                                 */

/*                                                                  */

/* 参数:                                                            */

/*            lpdwVirtualKey         Keycode 数组的指针             */

/*            nLength                Keycode 数组的大小             */

/*            bDisableKeyboard       是否屏蔽整个键盘               */

/*                                                                  */

/* 返回值:    TRUE 成功, FALSE 失败                                 */

/********************************************************************/

BOOL WINAPI StartMaskKey(LPDWORD lpdwVirtualKey, int nLength, BOOL bDisableKeyboard = FALSE)

{

     // 如果已经安装键盘钩子则返回 FALSE

     if (g_hHook != NULL) return FALSE;

     

     // 将用户传来的 keycode 数组保存在全局变量中

     g_lpdwVirtualKey = (LPDWORD)malloc(sizeof(DWORD) * nLength);

     LPDWORD tmpVirtualKey = g_lpdwVirtualKey;

     for (int i = 0; i < nLength; i++)

     {

         *tmpVirtualKey++ = *lpdwVirtualKey++;

     }

     g_nLength = nLength;

     g_bDisableKeyboard = bDisableKeyboard;

     

     // 安装底层键盘钩子

     g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, g_hInstance, NULL);

     if (g_hHook == NULL) return FALSE;

     return TRUE;

     

}

 

/********************************************************************/

/* 停止屏蔽键盘按键                                                 */

/*                                                                  */

/* 参数: (无)                                                       */

/*                                                                  */

/* 返回值:    TRUE 成功, FALSE 失败                                 */

/********************************************************************/

BOOL WINAPI StopMaskKey()

{

     // 卸载钩子

     if (UnhookWindowsHookEx(g_hHook) == 0) return FALSE;

     g_hHook = NULL;

     return TRUE;

}
 

 

     DEF文件(MaskKey.def):

 

EXPORTS

StartMaskKey  @1

StopMaskKey        @2
 

 

     上面就是DLL工程中主要的三个文件,工程类型为Win32 Dynamic-Link Library。从DEF文件可以看出,DLL共有两个导出函数:StartMaskKey和StopMaskKey。

StartMaskKey有三个参数,lpdwVirtualKey是一个指向DWORD数组的指针,该DWORD数组用来存放virtual-key code,nLength是该数组的大小,bDisableKeyboard是个逻辑值,如果为TRUE表示禁用整个键盘,默认为FALSE。使用正确的参数调用StartMaskKey,DLL可以将DWORD数组中每一个virtual-key code与键盘上对应的按键屏蔽掉,按这些键将完全没有反应(包括win键)。事实上,对于virtual-key code只要一个字节就可以表示了,但KBDLLHOOKSTRUCT结构中的vkCode是DWORD型,所以为求统一我也采用4个字节(DWORD)。纵然如此,微软还是在MSDN中强调了,virtual-key code的值必须是1到254之间的值,这点一定要注意。

StopMaskKey没有参数,表示停止屏蔽键盘按键。如果在程序中没有调用StopMaskKey停止屏蔽键盘按键,在进程或线程退出时将自动停止屏蔽,恢复原来的状态。当然进程和线程一定要正常退出,如果是被别的程序以TerminateProcess或TerminateThread等微软不太建议使用的野蛮手段结束进程或线程的话,就不太好办了。  :(

     下面是在VC中调用的例子:(两个Dialog的成员函数,对应两个按钮)

 

void CMaskKeyAppDlg::OnStartmaskkey()

{

     // 屏蔽 A, B, C, 上, 下, 左, 右及两个win键

     DWORD dwVK[] = {'A', 'B', 'C', VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_LWIN, VK_RWIN};

     int nLength = sizeof(dwVK) / sizeof(DWORD);

     StartMaskKey(dwVK, nLength);    

     

}

 

void CMaskKeyAppDlg::OnStopmaskkey()

{

     StopMaskKey();

     

}
 

 

     下面是在VB中调用的例子:(在窗体上添加2个CommandButton,并分别改名为cmdStartMask和cmdStopMask)

 

Option Explicit

Private Declare Function StartMaskKey Lib "MaskKey" (lpdwVirtualKey As Long, ByVal nLength As Long, Optional ByVal bDisableKeyboard As Boolean = False) As Long

Private Declare Function StopMaskKey Lib "MaskKey" () As Long

 

Private Sub cmdStartMask_Click()

    ' 屏蔽 A, B, C, 上, 下, 左, 右及两个win键

    Dim key(8) As Long

    key(0) = vbKeyA

    key(1) = vbKeyB

    key(2) = vbKeyC

    key(3) = vbKeyLeft

    key(4) = vbKeyRight

    key(5) = vbKeyUp

    key(6) = vbKeyDown

    key(7) = &H5B               ' 左边的win键

    key(8) = &H5C               ' 右边的win键

StartMaskKey key(0), UBound(key) + 1

End Sub

 

Private Sub cmdStopMask_Click()

    StopMaskKey

End Sub
 

 

     对于VB,如果是在VB的IDE环境中按F5启动程序,则必须调用StopMaskKey才能使键盘恢复状态,如果没有调用,则在退出VB的IDE环境时由DLL恢复键盘状态。对于编译后独立执行的VB程序,则和VC编译后的程序一样,无论是否调用StopMaskKey,都将在程序退出时由DLL自动卸载钩子,恢复键盘状态。

     其实,钩子并不是什么很深奥的技术,我写这个DLL的目的,也是为了我们在以后用到的时候,可以实行“拿来主义”。

     DLL源代码及VC和VB调用例程的下载地址:http://csdngoodname008.51.net/MaskKey.zip

==============================================================================================================================



标题   利用底层键盘钩子拦载任意按键(回调版)     选择自 goodname008 的 Blog  
关键字   利用底层键盘钩子拦载任意按键(回调版)
出处    
 
 
    前段时间我曾经写过一篇《利用底层键盘钩子屏蔽任意按键》,并放到了我的blog上。这篇文章的题目中把“屏蔽”改成了“拦截”,显然要比以前的版本强一些了。对于以前写的那个DLL,有一个不够理想的地方,就是仅仅能实现屏蔽。如果想在屏蔽之前加入一些“小动作”,就只能修改DLL,在LowLevelKeyboardProc函数中添加代码,实现新的功能。但这样显然不够灵活,这样的DLL也不具备一般性了。所以我自然而然地想到了回调,Windows中有很多需要回调函数的API,我们当然也可以写出这样的API,这样做的好处就是可以给DLL调用程序留下足够的接口。此时,DLL就像一个阀门,我们不关心的按键消息就把它放过去,只把我们关心的按键消息拦截下来,然后进一步处理,而这些处理的代码就写在DLL调用程序的回调函数中,这样做是最理想不过的了。

    相对于前一个版本,修改后的DLL源代码如下:

 

/********************************************************************/

/* 文件名: MaskKey.cpp                                              */

/*                                                                  */

/* 功能: 标准 DLL ---- 利用底层键盘钩子实现拦截键盘任意按键         */

/*                                                                  */

/* 作者: 卢培培 (goodname008)             时间: 2005.1.18           */

/*                                                                  */

/* BLOG: http://blog.csdn.net/goodname008                           */

/********************************************************************/

 

// 导出函数列表

// StartMaskKey

// StopMaskKey

 

#define _WIN32_WINNT  0x0500                   // 设置系统版本, 确保可以使用底层键盘钩子

 

#include "windows.h"

 

// 回调函数指针

typedef BOOL (CALLBACK* LPFNKEYBOARDPROC)(WPARAM, KBDLLHOOKSTRUCT*);

 

// 全局变量

LPDWORD       g_lpdwVirtualKey = NULL;         // Keycode 数组的指针

int           g_nLength = 0;                   // Keycode 数组的大小

BOOL          g_bDisableKeyboard = FALSE;      // 是否屏蔽整个键盘

HINSTANCE     g_hInstance = NULL;              // 模块实例句柄

HHOOK         g_hHook = NULL;                  // 钩子句柄

LPFNKEYBOARDPROC   g_lpfnKeyboardProc;         // 键盘钩子回调函数指针

 

// DLL 入口函数

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)

{

     // 保存模块实例句柄

     g_hInstance = (HINSTANCE)hModule;

     

     // 在进程结束或线程结束时卸载钩子

     switch (ul_reason_for_call)

     {

         case DLL_PROCESS_ATTACH:

              break;

         case DLL_THREAD_ATTACH:

              break;

         case DLL_PROCESS_DETACH:

 

         case DLL_THREAD_DETACH:

              free(g_lpdwVirtualKey);

              if (g_hHook != NULL) UnhookWindowsHookEx(g_hHook);

              break;

     }

    return TRUE;

}

 

// 底层键盘钩子函数

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)

{

     // 拦截键盘的某些按键, 如果 g_bDisableKeyboard 为 TRUE 则拦截整个键盘按键

if (nCode >= HC_ACTION)

     {

         KBDLLHOOKSTRUCT* pStruct = (KBDLLHOOKSTRUCT*)lParam;

         if (g_bDisableKeyboard)

              if (g_lpfnKeyboardProc(wParam, pStruct))

   return CallNextHookEx(g_hHook, nCode, wParam, lParam);

              else

                   return true;

 

         LPDWORD tmpVirtualKey = g_lpdwVirtualKey;

         for (int i = 0; i < g_nLength; i++)

         {

              if (pStruct->vkCode == *tmpVirtualKey++)

                   if (g_lpfnKeyboardProc(wParam, pStruct))

   return CallNextHookEx(g_hHook, nCode, wParam, lParam);

                   else

                       return true;

         }

         

     }    

 

     // 调用系统中的下一个钩子

     return CallNextHookEx(g_hHook, nCode, wParam, lParam);

}

 

/********************************************************************/

/* 开始拦截键盘按键                                                 */

/*                                                                  */

/* 参数:                                                            */

/*            lpdwVirtualKey              Keycode 数组的指针        */

/*            nLength                     Keycode 数组的大小        */

/*            bDisableKeyboard            是否拦截整个键盘          */

/*                                                                  */

/* 返回值:    TRUE 成功, FALSE 失败                                 */

/********************************************************************/

BOOL WINAPI StartMaskKey(LPDWORD lpdwVirtualKey, int nLength,

                             LPFNKEYBOARDPROC lpfnKeyboardProc, BOOL bDisableKeyboard = FALSE)

{

     // 如果已经安装键盘钩子则返回 FALSE

     if (g_hHook != NULL || nLength == 0) return FALSE;

     

     // 将用户传来的 keycode 数组保存在全局变量中

     g_lpdwVirtualKey = (LPDWORD)malloc(sizeof(DWORD) * nLength);

     LPDWORD tmpVirtualKey = g_lpdwVirtualKey;

     for (int i = 0; i < nLength; i++)

     {

         *tmpVirtualKey++ = *lpdwVirtualKey++;

     }

     g_nLength = nLength;

     g_bDisableKeyboard = bDisableKeyboard;

     g_lpfnKeyboardProc = lpfnKeyboardProc;

     

     // 安装底层键盘钩子

     g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, g_hInstance, NULL);

     if (g_hHook == NULL) return FALSE;

     return TRUE;

     

}

 

/********************************************************************/

/* 停止拦截键盘按键                                                 */

/*                                                                  */

/* 参数: (无)                                                       */

/*                                                                  */

/* 返回值:    TRUE 成功, FALSE 失败                                 */

/********************************************************************/

BOOL WINAPI StopMaskKey()

{

     // 卸载钩子

     if (UnhookWindowsHookEx(g_hHook) == 0) return FALSE;

     g_hHook = NULL;

     return TRUE;

}

 
 

 

     当然了,MaskKey.h头文件中也要加上:

 

// 回调函数指针

typedef BOOL (CALLBACK* LPFNKEYBOARDPROC)(WPARAM, KBDLLHOOKSTRUCT*);
 

 

     下面是在VC中调用的例子:(两个Dialog的成员函数,对应两个按钮,再加上一个回调函数)

 

// 全局键盘钩子回调函数

// 参数: action 标识键盘消息(按下,弹起), keyStruct 包含按键信息

BOOL CALLBACK KeyboardProc(WPARAM action, KBDLLHOOKSTRUCT* pKeyStruct)

{

     // 判断按键动作

     switch (action)

     {

         case WM_KEYDOWN:

 

              break;

         case WM_KEYUP:

 

              break;

         case WM_SYSKEYDOWN:

 

              break;

         case WM_SYSKEYUP:

 

              break;

     

     }

 

// 返回 true 表示继续传递按键消息

    // 返回 false 表示结束按键消息传递

     return false;

}

 

void CMaskKeyAppDlg::OnStartmaskkey()

{

     // 屏蔽 A, B, C, 上, 下, 左, 右及两个win键

     DWORD dwVK[] = {'A', 'B', 'C', VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_LWIN, VK_RWIN};

     int nLength = sizeof(dwVK) / sizeof(DWORD);

     StartMaskKey(dwVK, nLength, KeyboardProc);    

     

}

 

void CMaskKeyAppDlg::OnStopmaskkey()

{

     StopMaskKey();

     

}
 

 

    呵呵,这样是不是让看到这里的你很兴奋呢?!StartMaskKey加了一个参数,是个函数指针,这是我们非常熟悉的回调函数的使用方法。DLL中的StartMaskKey函数收到这个函数指针后保存在了g_lpfnKeyboardProc变量中,然后在LowLevelKeyboardProc中一旦发现了要拦截的按键,就会通过函数指针调用回调函数,将控制权完全交回给DLL的调用程序,由回调函数KeyboardProc进一步处理(播放一小段音乐,还是执行个什么有意思的程序,亦或是重启关机什么的。呃,随你便了。:D),action参数用来标识键盘消息(按下或弹起),pKeyStruct参数包含了丰富的按键信息,其实就是系统传给LowLevelKeyboardProc的lParam,我又把它原封不动地传给了KeyboardProc,呵呵。最重要的就是回调函数的返回值了,它就像阀门的开关一样,将决定这个按键消息的命运。从DLL中的LowLevelKeyboardProc函数的流程可以看出,如果回调函数KeyboardProc的返回值为true则表示把该按键消息继续传递给系统中的下一个钩子;如果为false则表示结束该按键消息的传递,此时将会起到拦截按键的效果。

    用VB的人可能有些不耐烦了,别着急,上篇文章在最后给出了VB调用的例程,此篇当然不能缺少这部分了。下面是在VB中调用的例子:(在窗体上添加2个CommandButton,并分别改名为cmdStartMask和cmdStopMask)

     

Option Explicit

Private Declare Function StartMaskKey Lib "MaskKey" (lpdwVirtualKey As Long, ByVal nLength As Long, ByVal lpfnKeyboarProc As Long, Optional ByVal bDisableKeyboard As Boolean = False) As Long

Private Declare Function StopMaskKey Lib "MaskKey" () As Long

 

Private Sub cmdStartMask_Click()

    ' 屏蔽 A, B, C, 上, 下, 左, 右及两个win键

    Dim key(8) As Long

    key(0) = vbKeyA

    key(1) = vbKeyB

    key(2) = vbKeyC

    key(3) = vbKeyLeft

    key(4) = vbKeyRight

    key(5) = vbKeyUp

    key(6) = vbKeyDown

    key(7) = &H5B               ' 左边的win键

    key(8) = &H5C               ' 右边的win键

    StartMaskKey key(0), UBound(key) + 1, AddressOf KeyboardProc

End Sub

 

Private Sub cmdStopMask_Click()

    StopMaskKey

End Sub
 

 

    窗体模块的代码和以前的例程几乎一样,只是在调用StartMaskKey函数时加了一个参数:AddressOf KeyboardProc。在VB中用过回调函数的人对这东西绝不会陌生,AddressOf是一个一元运算符,后面接一个函数名,它的功能就是获得指定函数的指针。但有一点必须注意,该回调函数(此例中为KeyboardProc)必须写在VB的标准模块中,标准模块的代码如下:

     

Option Explicit

 

Private Const WM_KEYDOWN = &H100

Private Const WM_KEYUP = &H101

Private Const WM_SYSKEYDOWN = &H104

Private Const WM_SYSKEYUP = &H105

 

Public Type KBDLLHOOKSTRUCT

    vkCode As Long              ' 虚拟按键码(1--254)

    scanCode As Long            ' 硬件按键扫描码

    flags As Long               ' flags

    time As Long                ' 消息时间戳

    dwExtraInfo As Long         ' 额外信息

End Type

 

Public Enum KEYACTION

    ACTION_KEYDOWN = WM_KEYDOWN

    ACTION_KEYUP = WM_KEYUP

    ACTION_SYSKEYDOWN = WM_SYSKEYDOWN

    ACTION_SYSKEYUP = WM_SYSKEYUP

End Enum

 

' 全局键盘钩子回调函数

' 参数: action 标识键盘消息(按下,弹起), keyStruct 包含按键信息

Public Function KeyboardProc(ByVal action As KEYACTION, keyStruct As KBDLLHOOKSTRUCT) As Boolean

    Select Case action

        Case ACTION_KEYDOWN

            Debug.Print keyStruct.vkCode, "按下键盘按键"

        Case ACTION_KEYUP

            Debug.Print keyStruct.vkCode, "弹起键盘按键"

        Case ACTION_SYSKEYDOWN

       

        Case ACTION_SYSKEYUP

       

    End Select

   

    ' 返回 True 表示继续传递按键消息

    ' 返回 False 表示结束按键消息传递

    KeyboardProc = False

End Function
 

 

    和VC版的调用例程差不多,只是把语法翻译成了VB的,这个VB标准模块中的KeyboardProc有没有点MFC消息映射函数的味道呢?!   :D
    需要注意的是,VB的回调函数必须写在标准模块中。细心的人还可能会发现,我对action参数作了一点小手脚,改成了一个枚举类型,这主要是为了易于理解。
    OK,要写的就这么多了,关于全局键盘钩子的内容我也想告一段落了。利用VC编写的DLL,VB也可以方便地实现全局键盘钩子了。当然,这不仅仅局限于键盘钩子,利用这种方法可以实现任何类型的钩子。
    DLL源代码及VC和VB调用例程的下载地址: http://csdngoodname008.51.net/MaskKeyCB.zip
 
 
 

2005-6-28 9:22:23   

 2008-3-4 10:14:04    Windows钩子函数的概念和实现方法

Windows钩子函数的概念和实现方法
首先我们必须大致了解Windows的基本运作机理,Windows作为一个多任务操作系统,它是分有层次概念的,运行在最底下的称为Ring 0层,在这一层里基本上都是一些硬件驱动程序和Windows的总内核,一般的应用程序极少极少运行在这层,当然也有例外,例如调试软件SoftICE(不过基本上这个软件的作用是Crack软件而不是调试)、还原精灵还有分区魔法大师,就是运行在Ring 0层的,另外就是著名的CIH病毒。运行在Ring 0级的程序能够对所有硬件进行直接地址级访问,所受到的限制也最小。

消息(Message)传递是Windows独有的一种机制,因为Windows规定运行在Ring 0以上的程序是没有权利知道究竟硬件发生了怎样的中断变化的,Windows统一将这些中断变化封装成一系列的消息(黑箱作业,也就是常说的Black Box),比如鼠标移动,系统产生一个OnMouseMove消息(但这条消息从何而来,相关的硬件中断向量是什么,程序无从得知),OnMouseMove这条消息最后送达每一个窗口程序以供处理。在更高层次的地方,比如说控件级,所有的消息还被封装成一系列“事件”,比如TextBox控件有KeyPress事件,实际上,这些事件都是林林种种的消息映射。事件的概念使得程序员能够更加傻瓜化地进行编程,但是从另一个角度来说,这种黑箱作业也使得程序员过分依赖系统的安排,限制了程序员的思维,举个例子,Windows为按钮控件封装了大部分常用的属性和事件,完成一般的常规妈作是没有问题的,但是很遗憾,或许是Windows的疏忽,按钮控件的字体颜色永远默认是黑色,而且Windows没有为此提供一个专门的接口来修改,碰到这种情况,程序员就会非常头疼。

钩子函数(Hook Function),就像一把钩子,它的作用是将消息在抵达窗口程序之前先钩到一个地方以便程序员进行分析,这个地方称为挂接函数链,消息在这里先被一系列的函数处理然后由程序员决定是否交还给Windows系统,在这里,你可以“吞噬”(Lickup)一些你不希望发生的消息,比如说你吞掉所有的键盘消息而不交还给系统,那么键盘将会失灵。当然,经过了这道周折,系统效率将会有极其微小的降低,但是,由于Windows规定所有不运行在Ring 0层的程序都不能直接访问硬件中断,因此作为一种中断驱动程序的补充,钩子函数在很多场合下是非常有用的,有时候甚至是唯一的方法。

下面就以键盘拦截为例讲述钩子函数的使用方法:

首先定义以下API:


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

SetWindowsHookEx,这个函数是一切钩子函数的根本,其作用是通知Windows进行钩子妈作并定义钩子函数。
参数1,idHook为定义需要进行的拦截类型,是键盘拦截?鼠标拦截?还是别的。如 WH_KEYBOARD捕捉键盘 消息,而WH_MOUSE捕捉鼠标消息。
参数2,lpfn为该挂接函数链的首地址指针,因为VB是没有指针这种数据类型的所以用Long 代替。lpfn为 钩 子 函 数 , 在 VB中 可 以 使 用 AddressOf获 得 钩 子 函 数 的 地 址 。 这 个 函 数 因 为 钩 子 类 型不 同 而 有 所 不 同 。 如 键 盘 钩 子 为 :
  Public Function KeyboardProc(ByVal nCode As Long, _
   ByVal wParam As Long, _
   ByVal lParam As Long) As Long
  如 果 Code不 为 0, 钩 子 函 数 必 须 调 用 CallNextHookEx, 将 消 息 传 递 给 下 面 的 钩 子 。 wParam和 lParam不 是 按 键 。

参数3,hmod为创建钩子函数那个实体的句柄,即你的程序本身的句柄(handle),hmod用于全局钩子,VB要实 现钩子,必须设为0。
(关于句柄:每一个程序都有一个ID号称为进程,句柄则分得更细,每一个进程里的每一个控件的ID号称为句柄。
比如一个程序既有输入框又有下拉条,那么下拉条和输入框都有自己的ID号,这个称为句柄。)
参数4,dwThreadId ,为监控代码,0表示全局监控,dwThreadId用 于 线 程 钩 子 VB中 可 以 设 置 为 App.ThreadID。

UnhookWindowsHookEx 函数为释放钩子,将钩子函数所占用的资源交还给系统,起一个简单的清道夫功能。


Private Declare Sub CopyMemory Lib "kernel32" _
Alias "RtlMoveMemory" _
(pDest As Any, _
pSource As Any, _
ByVal cb As Long)

CopyMemory 作用是将内存里的某一块数据pSource拷贝到另一个地址pDest。最后一个参数cb表示拷贝内容的字节大小。


Private Declare Function GetAsyncKeyState Lib "user32" _
(ByVal vKey As Long) As Integer

GetAsyncKeyState作用是获得各种辅助功能键的状态(如CTRL,SHIFT什么的)。

Private Declare Function CallNextHookEx Lib "user32" _
(ByVal hHook As Long, _
ByVal nCode As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long

CallNextHookEx 挂钩函数拦截了某条消息后,由CallNextHookEx决定是否将这些消息送还给Windows系统。

Private Type KBDLLHOOKSTRUCT
vkCode As Long
scanCode As Long
flags As Long
time As Long
dwExtraInfo As Long
End Type

KBDLLHOOKSTRUCT为键盘钩子的结构体定义,关于该结构的成员,我没有从API函数帮助库里找到任何资源,不过大概猜也能猜出来。
成员1:vkCode为虚拟键码
成员2:scanCode为扫描码
成员3:flags为功能键状态
成员4:扩展信息?
实际上本例中我们只是需要简单的知道vkCode然后用chr函数置换成字符即可,所谓的vkCode实际上和ASCIIC码是一一对应的。


Private Const HC_ACTION = 0
Private Const LLKHF_EXTENDED = &H1
Private Const LLKHF_INJECTED = &H10
Private Const LLKHF_ALTDOWN = &H20
Private Const LLKHF_UP = &H80
Private Const WH_KEYBOARD_LL = 13&

Public Const VK_TAB = &H9
Public Const VK_CONTROL = &H11
Public Const VK_ESCAPE = &H1B
Public Const VK_DELETE = &H2E


以上10个定义为常量定义,常量定义没有什么特别好说的,仅仅是帮助记忆而已。你完全可以在使用VK_DELETE的地方使用&H2E,如果你觉得&H2E比VK_DELETE更容易理解的话...


Public KeyboardHandle As Long
全局变量KeyboardHandle 为键盘钩子函数句柄,这个变量在开始挂钩的时候产生,结束挂钩的时候需要用它进行清场。

好了,现在我们开始工作:

首先创建一个工程,因为我们需要一个实体来运行我们的钩子函数,所以必须创建一个工程,在窗体部分填写:

Private Const WH_KEYBOARD_LL = 13&

Windows规定,键盘拦截的ID号为13号拦截。

Public Sub HookKeyboard()
KeyboardHandle = SetWindowsHookEx( _
WH_KEYBOARD_LL, AddressOf KeyboardCallback, _
App.hInstance, 0&)

End Sub

定义一个不带参数的子程序HookKeyboard,
KeyboardHandle 存储 钩子函数所产生的ID号,这个在清场的时候需要用到。
SetWindowsHookEx 的4个参数,
第一个,WH_KEYBOARD_LL, 正如前面定义的常量,它为13
第二个,AddressOf KeyboardCallback,这是自VB5以来的一次革命,VB5之后增加了一个保留字AddressOf,它的作用是获取某一个函数的首地址指针,在VB5之前的版本是没有这个功能的,有了AddressOf,大大扩展了VB的功能,才使得VB能够调用绝大部分API函数。因为VB本身并不存在指针。此行的意思是获得挂接函数链的首地址。
第三个,App.hInstance,App也是VB的保留字,表示本程序本身,如App.Path表示本程序当前目录,App.hInstance表示的是本程序本身的句柄,关于句柄前面已有所描述。
第四个,0,表示全局拦截,意思就是拦截所有窗口下的键盘输入。

定义完钩子函数,下面进入函数的具体实现,这些函数属于全局函数,所以不能在窗体级定义,必须降低到“模块”级别,现创建一个模块,然后开始编写,注:在VB里调用API几乎都是模块级的,很少出现在窗体级。实际不必深究这个问题,只要记住凡是调用API就用模块来编写就没错了。

Public Function KeyboardCallback(ByVal Code As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Static Hookstruct As KBDLLHOOKSTRUCT
If (Code = HC_ACTION) Then
Call CopyMemory(Hookstruct, ByVal lParam, Len(Hookstruct))

If (IsHooked(Hookstruct)) Then
KeyboardCallback = 1
Exit Function
End If

End If

KeyboardCallback = CallNextHookEx(KeyboardHandle, _
Code, wParam, lParam)

End Function

函数KeyboardCallback,是整个钩子函数的核心,
参数1:Code,表示拦截层次,之前我们已经说过,如果Code为0,则拦截所有窗口的键盘输入。
参数2:wParam表示是何种Windows消息
参数3:lParam表示某条Windows消息的具体内容的指针,它实际指向存储那个内容的内存地址。
下面详细叙述关于wParam和lParam:
wParam和lParam是Windows消息机制的两个最重要参数,整个Windows依靠这两个参数传递各种各样的消息,首先是wParam,它表示此次的消息类型是什么?是键盘?是鼠标?键盘里又分按下还是抬起,鼠标里又分是单击还是双击,等等。lParam是一个指针,它指向本条消息所存储的信息的内存区域的首地址,很显然,这个地址存放的东西是很灵活的,比如鼠标消息,那么这里可能存放的是各键的状态或者光标的X,Y座标。换成键盘消息,则是键码等等。总之,wParam区分了类别,lParam存放了该类别所存储的信息。因为VB没有指针,好在这里并不需要更多的指针妈作,只是记录一个首地址,所以可以用Long来代替。

注:在本程序里,wParam始终为256和257,257表示抬起键盘,256表示按下键盘,lParam每次运行都不一定一样,因为每次系统重启Windows都会重新定义消息指针,但是就一次进入而言,这个值是不会改变的,退出之后再进就有可能发生变化了。


Static Hookstruct As KBDLLHOOKSTRUCT
定义一个局部静态结构体实例,结构体为KBDLLHOOKSTRUCT。


If (Code = HC_ACTION) Then
Call CopyMemory(Hookstruct, ByVal lParam, Len(Hookstruct))

If (IsHooked(Hookstruct)) Then
KeyboardCallback = 1
Exit Function
End If

End If

If Code=HC_ACTION,这条鉴别Windows的消息来源,实际上我们之前已经将HC_ACTION定义为0了,所以所有键盘消息都将从此通过,那么,这个鉴别的意义何在呢?因为有可能还有别的键盘拦截程序等待着这些消息,我们不应该将这些消息据为己有,而是该交还给系统。

Call CopyMemory(Hookstruct, ByVal lParam, Len(Hookstruct))

这里用到了CopyMemory,作用是将lParam地址的内容复制到本地变量Hookstruct里来,这里又是VB的一个弊端,因为没有指针,所以必须动用到CopyMemory来完成这个使命。


If (IsHooked(Hookstruct)) Then
KeyboardCallback = 1
Exit Function
End If

IsHooked是我们自己定义的一个函数,该函数过滤了我们不希望出现的键盘码,比如说我们现在就是要不允许用户输入A,那么就可以在IsHooked里将A吞噬掉,那么键盘就永远打不出A来了。该函数返回0或者1,1表示此键确为我们不想要的字符,那么吃掉这条消息,不将它交还系统,Exit Function直接退出函数。如果不是,则略过。


可以看到,在If语句的最后是:

KeyboardCallback = CallNextHookEx(KeyboardHandle, _
Code, wParam, lParam)

表示:如果前面的条件都不成立,那么说明这条消息并非我们需要的消息,此时我们将此消息释放,用CallNextHookEx交还给系统。

下面我们进行前面提到的IsHooked函数的定义,这个函数主要是过滤我们不想要的那些键或者键盘组合,这里我们屏蔽了Alt+TAB组合:

Public Function IsHooked(ByRef Hookstruct As KBDLLHOOKSTRUCT) _
As Boolean

If (KeyboardHook Is Nothing) Then
IsHooked = False
Exit Function
End If
有时候CopyMemory也会发生意想不到的事情,所以,当KeyboardHook = Nothing (无值)的情况下,退出,略过该函数,以防不可预知的错误。

If (Hookstruct.vkCode = VK_TAB) And _
CBool(Hookstruct.flags And _
LLKHF_ALTDOWN) Then

IsHooked = True
Exit Function
End If

以上拦截了Alt+Tab的键盘组合,并将IsHooked返回True(就是1),表示本次按键确实符合了过滤原则,应该吞吃掉。

End Function


最后一步,释放钩子函数:

Public Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Long) As Long

Public Sub UnhookKeyboard()
If (Hooked) Then
Call UnhookWindowsHookEx(KeyboardHandle)
End If
End Sub

这个函数很简单,使用UnhookWindowsHookEx这个API释放之前由SetWindowsHookEx所定制的钩子函数。

一般而言,我们将钩子函数的创建放在窗体的Load部分,表示一进入程序立即启动钩子,而释放钩子则放在UnLoad部分,表示一旦程序关闭,我们需要立刻释放钩子所占用的资源。


结束语:

事实上,该例子演示了整个键盘拦截和屏蔽的功能,如果仅仅是需要知道用户按下了什么键,那么IsHooked函数是不需要的,并且我们也没有必要吞噬任何消息,只是让消息从我们的钩子函数里过一遍,知道它的值,然后再将此消息交还给系统即可,那样的话,程序将会更加简单。另外,在调试这类超越了VB编译环境本身的API程序时必须注意存盘,不断地存盘,因为VB环境和这些API函数同属系统级,因此它无法管理这些API,一旦出现问题,整个VB环境会在毫无预知的情况下造成全线崩溃的局面(比如VB编译环境的界面不提示任何内容自己就莫名其妙地突然消失等等),所以在按下RUN之前一定要记得存盘!!  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值