转贴
用DELPHI编制钩子函数
Windows消息管理机构提供了能使应用程序访问控制消息流μ
'c4所谓的钩子(HOOK)机制。钩子有多种,分别用于捕获某一特定类型或某一范围的消息。
如:键盘消息,鼠标消息等。我们这里仅以键盘钩子的使用为例,讨论在DELPHI下怎样编写
DLL程序和怎样在自己的程序中安装使用键盘钩子函数,并讨论了不同程序使用同一DLL文件
时怎样共享数据。
一、 钩子过滤函数的编写说明
由于钩子过滤函数必须在独立的模块中,也就是说我们必须首先生成一个DLL框架,然后再
在其中加入钩子函数代码以及其他相关函数代码。我们这里以键盘钩子过滤函数的编写为例
来说明。具体步骤如下:
1、先生成一个DLL筐2架
2、编写自己的键盘钩子过滤函数
钩子过滤函数必须是回调函数,其函数的 4?稳缦拢o
function KeyHookProc(
iCode:Integer;
wParam:WPARAM;
lParam:LPARAM ) : LRESULT; stdcall ;export ;
在生成的DLL框架中加入自己的键盘钩子处理函数处理键盘消息。
代码如下:…
if(iCode>=0) then begin
Result:=0; //初始化返回值
// 在这里加入自己的代码
end else
begin
Result:=CallNextHook(hOldKeyHook
iCode
wParam
lParam);
// hOldKeyHook是保存的原键盘过滤函数 5刂·
end;
3、 安装键盘钩子过滤函数
为安装一个钩子筥fd滤函数应调用SetWindowsHookEx函数(适用于Windows3.0的
SetWindowsHook钩子安装函数现在已经废弃不用)。该函数的原形如下:
HHOOK SetWindowsHookEx(
int idHook
// 安装的筥b3子类型
HOOKPROC lpfn
// 钩子过滤籂f数地址
HINSTANCE hMod
// 任务句柄
DWORD dwThreadId // 钩子用于的目的
);
需要说明的是:蚠a8常应该调用MakeProcInstance函数以获取一个输出函数的前导码的
入口地址,再将此地址作为SetWindowsHookEx的第二个参数lpfn。但由于Delphi提供了
"灵巧调用(smart callback)",使得MakeProcInstance可以省去,而直接将钩子过滤函数
名用作入口地址。这样当应用程序觃c3GetMessage或PeekMessage函数从消息队列中读消息
或有按键消息(WM_KEYDOWN或WM_KEYUP)要处理时,系统就要调用钩子过滤函数KeyHookProc
处理键盘消息。
4、 卸载钩子过滤函数。
当钩子函数不再需要时,应调用UnHookWindowsHookProc卸载安装的钩子以释放系统资源。
完整的程序清单如下?ba
Library KEYHOOK;
uses Windows;
const BUFFER_SIZE=16*1024;
const HOOK_MEM_FILENAME='SAMPLE KEY_HOOK_MEM_FILE';
const HOOK_MUTEX_NAME ='SAMPLE KEY_HOOK_MUTEX_NAME';
type
TShared=record
Keys : array[0..BUFFER_SIZE] of Char;
KeyCount : Integer;
end;
PShared=^TShared;
var
MemFile
HookMutex : THandle;
hOldKeyHook : HHook;
ProcSaveExit : Pointer;
Shared : PShared;
//键盘钩子过滤函数
function KeyHookProc(iCode: Integer; wParam: WPARAM ; lParam: LPARAM):LRESULT
; stdcall; export;
const KeyPressMask = $80000000;
begin
if iCode < 0 then
Result := CallNextHookEx(hOldKeyHook
iCode
wParam
lParam)
else begin
if ((lParam and KeyPressMask)= 0) then // 键按下
begin
Shared^.Keys[Shared^.KeyCount]:=Char(wParam and $00ff);
Inc(Shared^.KeyCount);
if Shared^.KeyCount>=BUFFER_SIZE-1 then Shared^.KeyCount:=0;
end;
iCode:=-1;
Result := CallNextHookEx(hOldKeyHook
iCode
wParam
lParam);
end;
end;
// 设置钩子过滤函数
function EnableKeyHook : BOOL ; export;
begin
Shared^.KeyCount:=0; //初始化键盘指针
if hOldKeyHook=0 then begin
hOldKeyHook := SetWindowsHookEx(WH_KEYBOARD
KeyHookProc
HInstance
0);
end;
Result := (hOldKeyHook <> 0);
end;
//撤消钩子过滤函数
function DisableKeyHook: BOOL ; export;
begin
if hOldKeyHook<> 0 then
begin
UnHookWindowsHookEx(hOldKeyHook); // 解除 Keyboard Hook
hOldKeyHook:= 0;
Shared^.KeyCount:=0;
end;
Result := (hOldKeyHook = 0);
end;
//取得键盘缓冲区中击键的个数
function GetKeyCount :Integer ; export;
begin
Result:=Shared^.KeyCount;
end;
//取得键盘缓冲区的键
function GetKey(index:Integer) : Char ; export;
begin
Result:=Shared^.Keys[index];
end;
//清空键盘缓冲区
procedure ClearKeyString ; export;
begin
Shared^.KeyCount:=0;
end;
//DLL的退出处理过程
procedure KeyHookExit; far;
begin
if hOldKeyHook <> 0 then DisableKeyHook;
UnMapViewOfFile(Shared); // 释放内存映象文件
CloseHandle(MemFile); // 关闭映象文件
ExitProc := ProcSaveExit;
end;
exports // 定义输出函数
EnableKeyHook
DisableKeyHook
GetKeyCount
ClearKeyString
GetKey;
begin
// DLL 初始化部分
HookMutex:=CreateMutex(nil
True
HOOK_MUTEX_NAME);
// 通过建立内存映象文件以共享内存
MemFile:=OpenFileMapping(FILE_MAP_WRITE
False
HOOK_MEM_FILENAME);
if MemFile=0 then
MemFile:=CreateFileMapping($FFFFFFFF
nil
PAGE_READWRITE
0
SizeOf(TShared)
HOOK_MEM_FILENAME);
Shared:=MapViewOfFile(MemFile
File_MAP_WRITE
0
0
0);
ReleaseMutex(HookMutex);
CloseHandle(HookMutex);
ProcSaveExit := ExitProc; // 保存DLL的ExitProc
ExitProc := @KeyHookExit; // 设置DLL新的ExitProc
end.
// 源代码结束
二、 在自己的程序中使用编制好的键盘钩子过滤函数。
钩子函数编制好后,使用起来其实很简单:首先调用SetWindowsHookEx安装自己的钩子过滤
函数,同时保存原先的钩子过滤函数地址。这时钩子函数就开始起作用了,它将按照你的要
求处理键盘消息。程序运行完毕或不再需要监视键盘消息时,调用UnHookWindowsHookProc函
数卸载所安装的钩子函数,同时恢复原来的钩子过滤函数地址。
下面就是使用在以上编制的钩子函数的例子:
unit Unit1;
interface
uses
Windows
Messages
SysUtils
Classes
Graphics
Controls
Forms
Dialogs
StdCtrls
ExtCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Panel1: TPanel;
bSetHook: TButton;
bCancelHook: TButton;
bReadKeys: TButton;
bClearKeys: TButton;
Panel2: TPanel;
procedure bSetHookClick(Sender: TObject);
procedure bCancelHookClick(Sender: TObject);
procedure bReadKeysClick(Sender: TObject);
procedure bClearKeysClick(Sender: TObject);
end;
var Form1: TForm1;
implementation
{$R *.DFM}
function EnableKeyHook : BOOL ; external 'KEYHOOK.DLL';
function DisableKeyHook : BOOL ; external 'KEYHOOK.DLL';
function GetKeyCount : Integer ; external 'KEYHOOK.DLL';
function GetKey(idx:Integer) : Char ; external 'KEYHOOK.DLL';
procedure ClearKeyString ; external 'KEYHOOK.DLL';
procedure TForm1.bSetHookClick(Sender: TObject); // 设置键盘钩 7ó
begin
EnableKeyHook;
bSetHook.Enabled :=False;
bCancelHook.Enabled:=True;
bReadKeys.Enabled :=True;
bClearKeys.Enabled :=True;
Panel2.Caption:=' 键盘钩子已经设置';
end;
procedure TForm1.bCancelHookClick(Sender: TObject); // 卸载键盘钩子
begin
DisableKeyHook;
bSetHook.Enabled :=True;
bCancelHook.Enabled:=False;
bReadKeys.Enabled :=False;
bClearKeys.Enabled :=False;
Panel2.Caption:=' 键盘钩子没有设置';
end;
procedure TForm1.bReadKeysClick(Sender: TObject); // 取得击键的历史记录
var i:Integer;
begin
Memo1.Lines.Clear; // 在Memo1中显示击键历史记录
for i:=0 to GetKeyCount-1 do
Memo1.Text:=Memo1.Text+GetKey(i);
end;
procedure TForm1.bClearKeysClick(Sender: TObject); // 清除击键历史记录
begin
Memo1.Clear;
ClearKeyString;
end;
end.
// 源代码结束
三、 Windows95下DLL中实现共享内存
在上面的钩子函数所在的DLL文件中,需要使用共享内存,即,所有击键的记录存储在同一
个数据段中。为什么要这样做呢?这是因为Windows95的DLL调用方法与Windows3.X的方法不
同。每个进(线)程在登录某动态连接库时都会为该动态连接库传入一个新的实例句柄
(即DLL数据段的句柄)。这使得DLL各个实例之间互不干扰,但是这对那些所有DLL实例共
享一组变量带来一些困难。为了解决这个问题,我们在这儿通过建立内存映射文件的方法来
解决。即使用Windows的OpenFileMapping、CreateFileMapping和
MapViewOfFile三个函数来实现。使用方法如下:
…
MemFile是THandle类型,Shared是指针类型,HOOK_MEM_FILENAME是一常量串
…
MemFile:=OpenFileMapping(FILE_MAP_WRITE
False
HOOK_MEM_FILENAME); //打开内存映射文件
if MemFile=0 then //打开失败则衉c2建内存映射文件
MemFile:=CreateFileMapping($FFFFFFFF
nil
PAGE_READWRITE
0
SizeOf(TShared)
HOOK_MEM_FILENAME);
//映射文件到变量
Shared:=MapViewOfFile(MemFile
File_MAP_WRITE
0
0
0);
到此为止,你已经知道用Delphi编制钩子函数有多么容易。最后不得不提醒大家:钩子函数
虽然功能比较强,但如果使用不当将会严重影响系统的效率,所以要尽量避免使用系统钩子
。非要使用不可时也应该格外小心,应使之尽可能小地影响系统的运行。