Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
-Delphi/Pascal
-CBuilder/C++
-C#Builder/C#
-JBuilder/Java
-Kylix
Member Area
-Home
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Login/Logout
-Become a Member
-Why sign up!
-Newsletter
-Chat Online!
-Indexes NEW!!
Employment
-Build your resume
-Find a job
-Post a job
-Resume Search
Contacts
-Contacts
-Feedbacks
-Link to us
-Privacy/Disclaimer
Embarcadero
Visit Embarcadero
Embarcadero Community
JEDI
Links
How to create a system wide windows hook Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
01-Oct-02
Category
Win API
Language
Delphi 2.x
Views
129
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			Author: Jonas Bilinkevicius 

How to create a system wide windows hook

Answer:

The following example demonstrates creating a system wide windows hook under Win32. 
The example provides both the code for the system hook dll and an example 
application. The hook function that we will create will also demonstrate advanced 
coding techniques such as sharing global memory across process boundaries using 
memory mapped files, sending messages from the key hook function back to the 
originating application, and dynamic loading of a dll at runtime.

The example keyboard hook that we create will keep a count of the number of 
keystrokes a user enters on the keyboard. Further, we will demonstrate trapping the 
enter key, and passing a message back to the application that initiated the 
keyboard hook each time the enter key is pressed. Finally, we will demonstrate 
trapping the left arrow key and instead of letting it through to the current 
application, we will instead replace it with a right arrow keystroke. (Note: that 
this can cause much confusion to a unsuspecting user).

Adding a hook to the windows system involves calling the Windows API function 
SetWindowsHookEx() and passing it the type of hook you wish to install, and address 
of the hook function you are installing. System wide hook functions are required to 
reside in a dynamic link library, since they must be mapped into each process on 
the system. The SetWindowsHookEx() function adds your hook function into the 
Windows "hook chain", returning a handle (or id) of the hook you are installing. 
You will use this handle to identify your hook to windows, and to remove your hook 
when you are done trapping the keyboard.

The Windows "hook chain" is a linked list of functions that Windows uses to keep 
track of all the installed hooks, allowing multiple hooks to be installed at any 
given time. Occasionally, Windows will ask your hook function to call the next hook 
in the chain, allowing all the hooks an opportunity to function. When we do call 
the next hook in the chain, we will need to identify ourselves by passing the 
handle of our hook function to the next hook.

Creating a Windows hook requires special handling under Win32, since the dll must 
be mapped (on the fly) into the process space of every application that receives 
keystrokes. Normally, this is not an issue, however, when operating inside a 
keyhook procedure, global variables (such as your hook handle) must be preserved 
while the dll is mapped into other process spaces. Under Win16, this would not be a 
program, since dlls had a single data segment that was shared across all process 
mappings. Under Win32, each mapping of the dll receives its own data segment. This 
means that as the dll that contains the keyboard hook is mapped into each process 
that receives keystrokes, it receives a new data segment, and new unitialized 
variables with it. This is a problem, since global variables (such as your hook 
handle) must be preserved across process mappings. To solve this problem, we will 
take advantage of Win32's ability to memory map variables from the system paging 
file.

Each time our dll is mapped into a process, the DllMain() function in our dll will 
be called by windows, with a parameter flag indicating the reason for the call. 
When we receive the DLL_PROCESS_ATTACH flag (indicating our dll is getting mapped 
into a different process), we will create a file mapping to the system paging file 
and get a pointer to our memory mapped variables. When we receive the 
DLL_PROCESS_DETACH flag (indicating our dll is getting un-mapped from a process), 
we will free our file mapping of the system paging file. The variables we will need 
to keep track of (and have access to from both the dll and the application that 
originally loaded the keyboard hook) are placed in a record structure called 
THookRec. The THookRec structure has the following fields:



TheHookHandle:

The handle (id) of the Keyboard hook that we set. We will need access to this 
variable during the execution of the keyhook function, to identify ourselves to 
windows when we are asked to call the next hook in the hook chain. We will also 
need access to this variable when we remove our hook. Finally, the originating 
application that will receive the messages from our hook function can access this 
variable to see if and when the hook is active.



TheAppWinHandle:

While this variable is not used in our example dll or application, it is a starting 
place for adding additional messaging capabilities between the hook function and 
your application that initiates the hook. It can also be useful for determining if 
the hook is functioning while mapped into the context of the initiating application.



TheCtrlWinHandle:

This variable will hold the handle to a button control in our initiating 
application. We will use this handle to send messages from the keyboard hook 
function to the button control. Every time the enter key is pressed, we will send a 
WM_KEYDOWN and a WM_KEYUP message to the button and a key value of 0 (zero). We 
will trap the OnKeyDown event in the button control, and keep count of the number 
of times the user presses the enter key.



TheKeyCount:

This variable will keep track of the total number of key presses made by the user. 
Obviously our keyhook will need access to this variable to increment its value, and 
the originating application that will receive the messages from our hook function 
will want to access this variable to display real time results.



The DLL contains the following functions:

MapFileMemory:
Creates a system paging file mapping object and initializes a pointer to our 
mapping variable of type THookRec.

UnMapFileMemory:
Frees the system paging file mapping object and mapping variable created by the 
MapFileMemory() function.

GetHookRecPointer:
An exported function that returns a pointer to the mapping variable created by the 
MapFileMemory() function. The initiating application can both set and examine this 
memory block, and effectively share memory that is used by our hook function during 
the time the hook function is operating in the context of another process space.

KeyBoardProc:
The actual hook function. This function receives both keydown, and keyup messages 
as well as a message from windows indicating we should call the next hook in the 
windows "hook chain". This function increments TheKeyCount field of the memory 
mapped THookRec structure if the keystroke we are processing is a keyup message. If 
the key being processed is the enter key, we will fire the OnKeyDown event of the 
window provided in "TheCtrlWinHandle" field of the memory mapped THookRec 
structure. Finally, if the left arrow key is pressed, we will swallow the 
keystroke, and instead send a right arrow key stroke to the application. Note that 
the following variables and initializing code has been included in this function 
for your convience. The variables have been commented out in the code (as not to 
compile). To use them, simply remove the comments in the code:

IsAltPressed  {Determines if the Alt key is currently down}
IsCtrlPressed  {Determines if the Control key is currently down}
IsShiftPressed  {Determines if the Shift key is currently down}


StartKeyBoardHook:
An exported function that allows the application to initiate installing the 
keyboard hook.

StopKeyBoardHook:
An exported function that allows the application to initiate removing the keyboard 
hook.

DllEntryPoint:
The main entry point into our dll, allowing us to know when our dll is being mapped 
in, and out of, different application's address space.



Delphi Hook DLL Example:



1   library TheHook;
2   
3   uses
4     Windows, Messages, SysUtils;
5   
6   {Define a record for recording and passing information process wide}
7   type
8     PHookRec = ^THookRec;
9     THookRec = packed record
10      TheHookHandle: HHOOK;
11      TheAppWinHandle: HWND;
12      TheCtrlWinHandle: HWND;
13      TheKeyCount: DWORD;
14    end;
15  
16  var
17    hObjHandle: THandle; {Variable for the file mapping object}
18    lpHookRec: PHookRec; {Pointer to our hook record}
19  
20  procedure MapFileMemory(dwAllocSize: DWORD);
21  begin
22    {Create a process wide memory mapped variable}
23    hObjHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize,
24      'HookRecMemBlock');
25    if (hObjHandle = 0) then
26    begin
27      MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK);
28      exit;
29    end;
30    {Get a pointer to our process wide memory mapped variable}
31    lpHookRec := MapViewOfFile(hObjHandle, FILE_MAP_WRITE, 0, 0, dwAllocSize);
32    if (lpHookRec = nil) then
33    begin
34      CloseHandle(hObjHandle);
35      MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK);
36      exit;
37    end;
38  end;
39  
40  procedure UnMapFileMemory;
41  begin
42    {Delete our process wide memory mapped variable}
43    if (lpHookRec <> nil) then
44    begin
45      UnMapViewOfFile(lpHookRec);
46      lpHookRec := nil;
47    end;
48    if (hObjHandle > 0) then
49    begin
50      CloseHandle(hObjHandle);
51      hObjHandle := 0;
52    end;
53  end;
54  
55  function GetHookRecPointer: pointer stdcall;
56  begin
57    {Return a pointer to our process wide memory mapped variable}
58    result := lpHookRec;
59  end;
60  
61  {The function that actually processes the keystrokes for our hook}
62  
63  function KeyBoardProc(Code: integer; wParam: integer; lParam: integer): integer;
64    stdcall;
65  var
66    KeyUp: bool;
67    {Remove comments for additional functionability ... :
68  
69    IsAltPressed: bool;
70    IsCtrlPressed: bool;
71    IsShiftPressed: bool;
72    }
73  begin
74    result := 0;
75    case Code of
76      HC_ACTION:
77        begin
78          {We trap the keystrokes here}
79          {Is this a key up message?}
80          KeyUp := ((lParam and (1 shl 31)) <> 0);
81  
82          {Remove comments for additional functionability ... :
83  
84          {Is the Alt key pressed}
85          if ((lParam and (1 shl 29)) <> 0) then
86          begin
87            IsAltPressed := TRUE;
88          end
89          else
90          begin
91            IsAltPressed := FALSE;
92          end;
93          {Is the Control key pressed}
94          if ((GetKeyState(VK_CONTROL) and (1 shl 15)) <> 0) then
95          begin
96            IsCtrlPressed := TRUE;
97          end
98          else
99          begin
100           IsCtrlPressed := FALSE;
101         end;
102         {if the Shift key pressed}
103         if ((GetKeyState(VK_SHIFT) and (1 shl 15)) <> 0) then
104         begin
105           IsShiftPressed := TRUE;
106         end
107         else
108         begin
109           IsShiftPressed := FALSE;
110         end;
111         }
112 
113         {If KeyUp then increment the key count}
114         if (KeyUp <> FALSE) then
115         begin
116           Inc(lpHookRec^.TheKeyCount);
117         end;
118         case wParam of
119           {Was the enter key pressed?}
120           VK_RETURN:
121             begin
122               {if KeyUp}
123               if (KeyUp <> FALSE) then
124               begin
125                 {Post a bogus message to the window control in our app}
126                 PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYDOWN, 0, 0);
127                 PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYUP, 0, 0);
128               end;
129               {If you wanted to swallow the keystroke then return -1, else if you 
130 want 
131 							to allow the keystroke then return 0}
132               result := 0;
133               exit;
134             end; {VK_RETURN}
135           {If the left arrow key is pressed then lets play a joke!}
136           VK_LEFT:
137             begin
138               {if KeyUp}
139               if (KeyUp <> FALSE) then
140               begin
141                 {Create a UpArrow keyboard event}
142                 keybd_event(VK_RIGHT, 0, 0, 0);
143                 keybd_event(VK_RIGHT, 0, KEYEVENTF_KEYUP, 0);
144               end;
145               {Swallow the keystroke}
146               result := -1;
147               exit;
148             end; {VK_LEFT}
149         end; {case wParam}
150         {Allow the keystroke}
151         result := 0;
152       end; {HC_ACTION}
153     HC_NOREMOVE:
154       begin
155         {This is a keystroke message, but the keystroke message has not been removed
156 			 from the message queue, since an application has called PeekMessage() 
157 			specifying PM_NOREMOVE}
158         result := 0;
159         exit;
160       end;
161   end; {case code}
162   if (Code < 0) then
163     {Call the next hook in the hook chain}
164     result := CallNextHookEx(lpHookRec^.TheHookHandle, Code, wParam, lParam);
165 end;
166 
167 procedure StartKeyBoardHook stdcall;
168 begin
169   {If we have a process wide memory variable and the hook has not already been 
170 set...}
171   if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle = 0)) then
172   begin
173     {Set the hook and remember our hook handle}
174     lpHookRec^.TheHookHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyBoardProc,
175       hInstance, 0);
176   end;
177 end;
178 
179 procedure StopKeyBoardHook stdcall;
180 begin
181   {If we have a process wide memory variable and the hook has already been set...}
182   if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle <> 0)) then
183   begin
184     {Remove our hook and clear our hook handle}
185     if (UnHookWindowsHookEx(lpHookRec^.TheHookHandle) <> FALSE) then
186     begin
187       lpHookRec^.TheHookHandle := 0;
188     end;
189   end;
190 end;
191 
192 procedure DllEntryPoint(dwReason: DWORD);
193 begin
194   case dwReason of
195     Dll_Process_Attach:
196       begin
197         {If we are getting mapped into a process, then get a pointer to our 
198 				process wide memory mapped variable}
199         hObjHandle := 0;
200         lpHookRec := nil;
201         MapFileMemory(sizeof(lpHookRec^));
202       end;
203     Dll_Process_Detach:
204       begin
205         {If we are getting unmapped from a process then, remove the pointer to 
206 				our process wide memory mapped variable}
207         UnMapFileMemory;
208       end;
209   end;
210 end;
211 
212 exports
213   KeyBoardProc name 'KEYBOARDPROC',
214   GetHookRecPointer name 'GETHOOKRECPOINTER',
215   StartKeyBoardHook name 'STARTKEYBOARDHOOK',
216   StopKeyBoardHook name 'STOPKEYBOARDHOOK';
217 
218 begin
219   {Set our Dll's main entry point}
220   DLLProc := @DllEntryPoint;
221   {Call our Dll's main entry point}
222   DllEntryPoint(Dll_Process_Attach);
223 
224 end.



Application notes:

The test application we have created demonstrates loading the dll that contains the 
keyboard hook, installing the keyboard hook, displaying the total keystroke count 
and the number of times the enter key has been pressed (in real time), uninstalling 
the keyboard hook and unloading the dll.

The application code starts out by defining a form containing two labels, a button, 
and timer component. Once we install our hook function, we will start the timer, 
and upon every timer event, we will display in label1 the total number of 
keystrokes that have been entered by the user since the hook was set. The hook will 
also fire the button's OnKeyDown event each time the enter key is pressed, giving 
us the opportunity to display the total number of times the enter key has been 
pressed in the caption of label2.

After the form is defined, we then define the THookRec structure in the same manner 
as it is defined in the hook dll. Other variables we will use include: a handle 
variable used for loading the hook dll, and three function pointer variables used 
to call the GetHookRecPointer(), StartKeyBoardHook(), and StopKeyBoardHook() 
functions. Finally we define a pointer to a THookRec structure used to access the 
memory mapped variables used by the hook function, a variable to keep track of the 
number of times the enter key is pressed, and a variable used to indicate the 
success of loading the dll, getting its functions, and setting the hook.



The application logic goes something like this:

On form create, we will initialize our form's components, attempt to dynamically 
load the hook dll, and get the address of the GetHookRecPointer(), 
StartKeyBoardHook(), and StopKeyBoardHook() functions located in the hook dll. If 
we are successful, we will retrieve a pointer to THookRec structure used by the 
hook dll, we will then initialize structure, adding the handle of the button 
control so the keyboard hook will know which window control to call when the enter 
key is pressed. We will then attempt to start the keyboard hook. If we are 
successful, at setting the hook, we can then start the timer.

On form destroy, if we where previously successful in installing the windows hook 
and loading the hook dll, we will now uninstall the windows hook, and unload the 
KeyHook dll.

On the timer's timer event, we will simply display the total number of key presses 
in the form's label1 caption by accessing the KeyHook dll's THookRec structure.

On the Buttons KeyDown event, if the key value passed is zero we increment our 
EnterKeyCount variable and display the total number of times the enter key has been 
pressed by accessing the KeyHook dll's THookRec structure.


Delphi TestApp Example:


225 unit TestHk1;
226 
227 interface
228 
229 uses
230   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
231 StdCtrls,
232     ExtCtrls;
233 
234 type
235   TForm1 = class(TForm)
236     Label1: TLabel;
237     Label2: TLabel;
238     Timer1: TTimer;
239     Button1: TButton;
240     procedure FormCreate(Sender: TObject);
241     procedure FormDestroy(Sender: TObject);
242     procedure Timer1Timer(Sender: TObject);
243     procedure Button1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
244   private
245     { Private declarations }
246   public
247     { Public declarations }
248   end;
249 
250 var
251   Form1: TForm1;
252 
253 implementation
254 
255 {$R *.DFM}
256 
257 {Functions prototypes for the hook dll}
258 type
259   TGetHookRecPointer = function: pointer stdcall;
260 type
261   TStartKeyBoardHook = procedure stdcall;
262 type
263   TStopKeyBoardHook = procedure stdcall;
264 
265   {The record type filled in by the hook dll}
266 type
267   THookRec = packed record
268     TheHookHandle: HHOOK;
269     TheAppWinHandle: HWND;
270     TheCtrlWinHandle: HWND;
271     TheKeyCount: DWORD;
272   end;
273 
274   {A pointer type to the hook record}
275 type
276   PHookRec = ^THookRec;
277 
278 var
279   hHookLib: THandle; {A handle to the hook dll}
280   GetHookRecPointer: TGetHookRecPointer; {Function pointer}
281   StartKeyBoardHook: TStartKeyBoardHook; {Function pointer}
282   StopKeyBoardHook: TStopKeyBoardHook; {Function pointer}
283   LibLoadSuccess: bool; {If the hook lib was successfully loaded}
284   lpHookRec: PHookRec; {A pointer to the hook record}
285   EnterKeyCount: DWORD; {An internal count of the Enter Key}
286 
287 procedure TForm1.FormCreate(Sender: TObject);
288 begin
289   {Set our initial variables}
290   Timer1.Enabled := FALSE;
291   Timer1.Interval := 1000;
292   Label1.Caption := '0 Keys Logged';
293   Label2.Caption := '0 Enter Keys Logged';
294   EnterKeyCount := 0;
295   lpHookRec := nil;
296   LibLoadSuccess := FALSE;
297   @GetHookRecPointer := nil;
298   @StartKeyBoardHook := nil;
299   @StopKeyBoardHook := nil;
300   {Try to load the hook dll}
301   hHookLib := LoadLibrary('THEHOOK.DLL');
302   {If the hook dll was loaded successfully}
303   if hHookLib <> 0 then
304   begin
305     {Get the function addresses}
306     @GetHookRecPointer := GetProcAddress(hHookLib, 'GETHOOKRECPOINTER');
307     @StartKeyBoardHook := GetProcAddress(hHookLib, 'STARTKEYBOARDHOOK');
308     @StopKeyBoardHook := GetProcAddress(hHookLib, 'STOPKEYBOARDHOOK');
309     {Did we find all the functions we need?}
310     if ((@GetHookRecPointer <> nil) and (@StartKeyBoardHook <> nil) and
311       (@StopKeyBoardHook <> nil)) then
312     begin
313       LibLoadSuccess := TRUE;
314       {Get a pointer to the hook record}
315       lpHookRec := GetHookRecPointer;
316       {Were we successfull in getting a ponter to the hook record}
317       if (lpHookRec <> nil) then
318       begin
319         {Fill in our portion of the hook record}
320         lpHookRec^.TheHookHandle := 0;
321         lpHookRec^.TheCtrlWinHandle := Button1.Handle;
322         lpHookRec^.TheKeyCount := 0;
323         {Start the keyboard hook}
324         StartKeyBoardHook;
325         {Start the timer if the hook was successfully set}
326         if (lpHookRec^.TheHookHandle <> 0) then
327         begin
328           Timer1.Enabled := TRUE;
329         end;
330       end;
331     end
332     else
333     begin
334       {We failed to find all the functions we need}
335       FreeLibrary(hHookLib);
336       hHookLib := 0;
337       @GetHookRecPointer := nil;
338       @StartKeyBoardHook := nil;
339       @StopKeyBoardHook := nil;
340     end;
341   end;
342 end;
343 
344 procedure TForm1.FormDestroy(Sender: TObject);
345 begin
346   {Did we load the dll successfully?}
347   if (LibLoadSuccess = TRUE) then
348   begin
349     {Did we sucessfully get a pointer to the hook record?}
350     if (lpHookRec <> nil) then
351     begin
352       {Did the hook get set?}
353       if (lpHookRec^.TheHookHandle <> 0) then
354       begin
355         Timer1.Enabled := FALSE;
356         StopKeyBoardHook;
357       end;
358     end;
359     {Free the hook dll}
360     FreeLibrary(hHookLib);
361   end;
362 end;
363 
364 procedure TForm1.Timer1Timer(Sender: TObject);
365 begin
366   {Display the number of keystrokes logged}
367   Label1.Caption := IntToStr(lpHookRec^.TheKeyCount) + ' Keys Logged';
368 end;
369 
370 procedure TForm1.Button1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
371 begin
372   {Process message sent from hook dll and display number of time the enter key was 
373 pressed}
374   if (Key = 0) then
375   begin
376     Inc(EnterKeyCount);
377     Label2.Caption := IntToStr(EnterKeyCount) + ' Enter Keys Logged';
378   end;
379 end;
380 
381 end.


			
Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10

 

Advertisement
Share this page
Advertisement
Download from Google

Copyright © Mendozi Enterprises LLC