低级键盘钩子在delphi中的实现

昨天晚上,有个朋友告诉我他想屏蔽掉键盘的WIN键,问我有没有简单的办法,回来就做了一个简单的示例。
这个示例中使用到一个低级的键盘钩子,它是从windows2000引进的,所以必须在win2000以上的版本才被支持,而且关于低级键盘钩子的部分在delphi中并没有引入。所以对照C语言的资料将它转换为delphi语言的格式,下面是使用delphi格式声明的一个常量和一个数据结构:

const WH_KEYBOARD_LL = 13;
type
PBDLLHOOKSTRUCT = ^TBDLLHOOKSTRUCT;
TBDLLHOOKSTRUCT = record
vkCode: DWORD;
scanCode: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: DWORD;
end;

而对于HOOK的使用这里就不详细介绍了,只要在网上一搜,很多这方面的资料!这里简单介绍一下hook的基本原理:由于windows操作系统是一个基于消息的操作系统,我们与系统(包括硬件和操作系统或者其他软件)基本上都是通过消息来进行的。消息又有很多,硬件消息(如键盘按键消息和鼠标消息),软件消息(很多windows定义的通知消息或者我们自定义的消息),还有很多,这里就不多列举了!而我们在消息处理的时候都是使用诸如GetMessage或者其他windows提供的系统函数,其实这些函数的作用不仅仅表现在它们名字所表示的作用,它们还可以使用中断进入到系统空间中,以便系统做出进一步的处理(比如getmessage函数调用如果发现没有消息送入线程消息队列的话,系统便会发生一次调度,那么这个线程就被挂起了,直到等到下次调度发生时如果有消息送入队列就会重新获得CPU时间),这里系统的处理就包括很多了,其中就包括HOOK的处理,如果这个时候发现用户注册了某种钩子(所有用户注册的钩子都会保存在一个数据结构的链表中),那么函数在返回前先调用钩子的回调函数处理,如果回调返回TRUE的话,说明回调函数已经处理了这个消息事件并要求系统不需要再派发这个消息事件了,这个时候函数会等待下一个消息事件的到来而不会返回(阻塞了),那么我们用户程序就得不到这个消息事件的通知,这不就达到过滤的目的了吗?
好了,简单了解了钩子的原理,下面简单说说过程:
1.先安装钩子:

HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);

这个函数用于安装钩子,参数idHook表示你要安装什么类型的钩子(钩子类型有系统预先定义好了,系统只能处理已知的钩子类型);lpfn表示的是你的回调过程,如果发生了你安装类型的消息事件,windows系统会根据注册的这个函数地址来调用你的函数,并由函数返回的结构来决定是否过滤还是继续交给用户处理。这里再多说一下,如果我们要为所有的程序安装过滤函数的话,那么我们这个函数应该可以映射到所有程序的地址空间才可以使用,这个任务只有DLL文件才能完成,这就是全局钩子函数必须放在DLL中原因,如果是放在EXE中,那么只有我们自己的EXE的消息事件才能过滤;hMod参数是包含钩子函数的模块句柄;dwThreadId表示的是指定监视的线程ID,如果确定的话,那么钩子只对单个线程有效(注意:消息队列是以线程为单位的,而不是以窗口或者进程为单位的,虽然很多线程根本不使用队列),如果这个参数为0,那么就是作用全部的线程了,这个时候的钩子函数应该放在DLL中,否则也是无效的!
2.定义钩子函数:(这个步骤应该在最前面,为了条理好一点,放在第2条了^_^)

function ProcName(nCode: integer; wParam: integer; lParam: integer): LRESULT; stdcall;

以上是钩子函数的原型,这里要注意的就是返回值了,如果是我们确定要过滤的,我们返回1就可以退出函数了,如果不是我们感兴趣的话,返回值我们就不能确定了,应该交给系统,系统会去查找其他注册了钩子函数,返回值由他们去确定,所以返回值可以调用CallNextHookEx函数来确定。
3.卸载钩子:

LongBool UnhookWindowsHookEx(int idHook);

如果我们要退出钩子过滤的话,我们调用这个函数,它会使windows系统删除钩子链中的相应的钩子。

 

下面是实现一个低级键盘钩子的delphi代码,由于是低级钩子(处理的优先级最高),它这里有个特性,就是不用将钩子函数放在DLL中,只要我们的进程在运行(不一定要是活动的),windows系统总能够得到我们回调函数的入口的(我猜想在内部发生任务切换,最后还是会切换到我们的进程中来处理了,这个切换的过程可以达到使用DLL来映射回调函数到其他进程地址空间一样的效果!如果我们的进程关闭,即使我们的钩子没有正常卸载,钩子马上就不起作用了,因为系统已经找不到回调函数的入口地址了,即使它怎么切换任务都没有用^_^),这个示例很简单,只是屏蔽了WIN键(键盘上有左右两个WIN键)。

unit Unit1;

 

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

const WH_KEYBOARD_LL = 13;

type
PBDLLHOOKSTRUCT = ^TBDLLHOOKSTRUCT;
TBDLLHOOKSTRUCT = record
vkCode: DWORD;
scanCode: DWORD;
flags: DWORD;
time: DWORD;
dwExtraInfo: DWORD;
end;

TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;

function KeyboardProc(nCode: integer; wParam: integer; lParam: integer): LRESULT; stdcall;

var
Form1: TForm1;
Hook: HHOOK = 0;
implementation

{$R *.dfm}

function KeyboardProc(nCode: integer; wParam: integer; lParam: integer): LRESULT;
begin
if (nCode = HC_ACTION) then
begin
case wParam of
WM_KEYDOWN, WM_SYSKEYDOWN,
WM_KEYUP, WM_SYSKEYUP:
begin
if (PBDLLHOOKSTRUCT(lParam)^.vkCode = VK_LWIN) or
(PBDLLHOOKSTRUCT(lParam)^.vkCode = VK_RWIN) then
begin
result := 1;
exit;
end;
end;
end;
end;
result := CallNextHookEx(0, ncode, wparam, lparam);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Hook := SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, HInstance, 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if Hook 0 then
UnhookWindowsHookEx(Hook);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Button2Click(nil);
end;

end.

posted @ 2013-06-15 02:33  Max Woods  阅读(1188)  评论(0编辑  收藏  举报