
Why do we need hooking? There is OnMouseMove to get the position or OnKeyDown from my Form to get what key the user presses. Okay, the most interesting point of hooking is that you get to monitor events that initially not belong or intended for your application. For example, event OnMouseMove and OnKeyDown won't fired if you application is not the one has the focus. Now what if you need to know the mouse movements, or mouse clicks, or keyboard pressing even when your application is not having focus?
Please note that although the technique explain here can be used for keylogger application, but this tutorial does not approve or promote writing any application with intentions against law and moral/social norms. This is purely for knowledge sharing and educational purpose.
The Project
The application that we will develop is asked to answer the following requirement.
Whenever mouse pointer is over any of its form, even if the form is behind window from other application (ie. when the application is not having focus), it must make beep sound.
If you check this thread that need to be able to click through windows from other application, perhaps this technique can be one work around.
The Basics
To install your hook, you need to use Windows API SetWindowsHookEx. Go find its full explanation in Windows SDK or in MSDN. In brief we have to tell it what kind of event we want to monitor, and provide callback routine matches the event we want to monitor. When we finish with the hook (i.e. stop monitoring) we have to release the hook by calling Windows API UnhookWindowsHookEx along with hook handle we got previously from SetWindowsHookEx.
The Implementation
From the Basic section above, it sounds like we have three routines ready to write. So here we go.
type PData = ^TData; TData = packed record MouseHook: HHOOK; AppHandle: HWnd; end; var Data: PData; function MouseHookHandler(ACode: Integer; WParam: WParam; LParam: LParam): LResult; stdcall; var vMouseInfo: PMouseHookStruct; begin if ACode < 0 then Result := CallNextHookEx(Data^.MouseHook, ACode, WParam, LParam) else begin vMouseInfo := PMouseHookStruct(Pointer(LParam)); PostMessage(Data^.AppHandle, WM_CUSTOM_MOUSE, vMouseInfo^.pt.X, vMouseInfo^.pt.Y); // we dont want to block the mouse messages Result := 0; end; end; procedure HookMouse(const AppHandle: HWnd); stdcall; begin Data^.MouseHook := SetWindowsHookEx( WH_MOUSE // hook to mouse events , MouseHookHandler // the callback routine , HINSTANCE // library (dll) identifier , 0 ); Data^.AppHandle := AppHandle; end; procedure UnhookMouse; begin UnhookWindowsHookEx(Data^.MouseHook); Data^.MouseHook := 0; end;
MouseHookHandler
This is the callback function that gets called each time the system generates mouse events. Note that if you get ACode with value of 0, you have to IMMEDIATELY pass the processing to the next hook and do no processing. When we get our share of events (inside the else begin end block) we just parse the parameter to get mouse location and pass that information to our application main window (stored in Data^.AppHandle). Since we don't need to wait for the result we can safely use PostMessage here.
HookMouse
This procedure is the one that installs the mouse hook. It also store the passed application window handle into a variable for easy reference later.
UnhookMouse
This procedure is the one that uninstalls our mouse hook.
Dynamic-Link Library
These routines must be placed inside a dynamic-link library (dll) file. Otherwise we only get events belong to our application, not including events for other applications. Placing these routines inside a dll introduces one important issue: we want the Data variable to be global, i.e. shared among all applications loading the dll.
This issue can be solved by creating a map to the data variable. All applications must use the same map. With the same map, it will be guaranteed that the Data variable is shared (there is only a single instance of Data variable). And of course after we are done, we need to erase the map. Sounds like two new routines ready.
const MMF_KEY = '{D2B9306D-EB08-41F9-AC43-9D0DD84CBD4C}'; var MMF: THandle; procedure CreateGlobalData; begin Randomize; MMF := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(TGlobalData), MMF_KEY); if MMF = 0 then exit; Data := MapViewOfFile(MMF, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TData)); if Data = nil then CloseHandle(MMF); end; procedure DeleteGlobalData; begin if Data <> nil then UnmapViewOfFile(Data); if MMF <> INVALID_HANDLE_VALUE then CloseHandle(MMF); end;
constant MMF_KEY is required to guarantee that our map is the same across all applications that loads the dll. Because they all will execute CreateFileMapping API with the same key which giving the same map.
Now we have our library of map functions ready. Time to define when to execute them. The following routine will be executed every time a process/a thread use the dll, so it will be our best bet to manage the map.
procedure DLLEntry(dwReason: DWORD); begin case dwReason of // run when a process starts using our dll DLL_PROCESS_ATTACH: CreateGlobalData; // run when a process stop unload (stop using) our dll DLL_PROCESS_DETACH: DeleteGlobalData; end; end;
Now for the complete dll code:
library MouseHook; type PData = ^TData; TData = packed record MouseHook: HHOOK; AppHandle: HWnd; end; var Data: PData; const MMF_KEY = '{D2B9306D-EB08-41F9-AC43-9D0DD84CBD4C}'; WM_CUSTOM_MOUSE = WM_USER + 111; var MMF: THandle; function MouseHookHandler(ACode: Integer; WParam: WParam; LParam: LParam): LResult; stdcall; var vMouseInfo: PMouseHookStruct; begin if ACode < 0 then Result := CallNextHookEx(Data^.MouseHook, ACode, WParam, LParam) else begin vMouseInfo := PMouseHookStruct(Pointer(LParam)); PostMessage(Data^.AppHandle, WM_CUSTOM_MOUSE, vMouseInfo^.pt.X, vMouseInfo^.pt.Y); // we dont want to block the mouse messages Result := 0; end; end; procedure HookMouse(const AppHandle: HWnd); stdcall; begin Data^.MouseHook := SetWindowsHookEx( WH_MOUSE // hook to mouse events , MouseHookHandler // the callback routine , HINSTANCE // library (dll) identifier , 0 ); Data^.AppHandle := AppHandle; end; procedure UnhookMouse; begin UnhookWindowsHookEx(Data^.MouseHook); Data^.MouseHook := 0; end; procedure CreateGlobalData; begin Randomize; MMF := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(TGlobalData), MMF_KEY); if MMF = 0 then exit; Data := MapViewOfFile(MMF, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TData)); if Data = nil then CloseHandle(MMF); end; procedure DeleteGlobalData; begin if Data <> nil then UnmapViewOfFile(Data); if MMF <> INVALID_HANDLE_VALUE then CloseHandle(MMF); end; procedure DLLEntry(dwReason: DWORD); begin case dwReason of // run when a process starts using our dll DLL_PROCESS_ATTACH: CreateGlobalData; // run when a process stop unload (stop using) our dll DLL_PROCESS_DETACH: DeleteGlobalData; end; end; exports HookMouse , UnhookMouse ; begin DLLProc := @DLLEntry; DLLEntry(DLL_PROCESS_ATTACH); end.
Application Part
In this part we only need to deal with notification message from our dll and compare with the areas of our form. When the mouse is over one of our form, even if that form is under other application's window, we have to play beep sound.
You can start by creating a new Delphi project, and add several forms to it. Make sure all the forms have "Visible" property set to true. Add two buttons in the main form, give a button a title of Install Mouse Hook and Uninstall Mouse Hook for the other. In the form declaration add a few lines like the following.
const WM_CUSTOM_MOUSE = WM_USER + 111; type TForm1 = class(TForm) ... ... private // flag to see if we already have mouse over our form previously FMouseOver: Boolean; procedure HandleCustomMouseMsg(var AMsg: TMessage); message WM_CUSTOM_MOUSE; procedure Beeps; end; procedure HookMouse(const AppHandle: HWnd); stdcall external 'MouseHook.dll'; procedure UnhookMouse; stdcall external 'MouseHook.dll';
Note that we now declare a method named HandleCustomMouseMsg which will be fired whenever the main form receives windows message with ID WM_CUSTOM_MOUSE. Implement that method goes like:
procedure TForm1.HandleCustomMouseMsg(var AMsg: TMessage); function IsMouseWithinOurRange: Boolean; var i: Integer; begin Result := False; for i := 0 to Screen.FormCount-1 do with Screen.Forms[i] do if Visible then begin Result := (AMsg.WParam >= Left) and (AMsg.WParam <= Left + Width) and (AMsg.LParam >= Top) and (AMsg.LParam <= Top + Height); if Result then Break; end; end; begin if IsMouseWithinOurRange then begin // if we had handled this, just exit if FMouseOver then Exit; FMouseOver := True; Beeps; end else // mark that mouse is not over any of our forms FMouseOver := False; end;
Whenever we receive WM_CUSTOM_MOUSE message from our dll, it will contain mouse cursor coordinate. The X part passed in WParam part of the message, and the Y part passed in LParam part of the message. So now we can check (using local function IsMouseWithinOurRange) if the coordinate is above one of our forms or not. If it is, then we play beeps.
The Beeps method is simply playing a series of Beep.

procedure TForm1.Beeps; begin Beep; Beep; Beep; end;
For the OnClick event handler of "Install Mouse Hook" put the following codes:
procedure TForm1.Button1Click(Sender: TObject); begin HookMouse(Self.Handle); end;
It tells our dll to install the mouse hook and to send the windows message notifications to our window handle.
And lastly the button of "Uninstall Mouse Hook" we give the following code for its OnClick event.
procedure TForm1.FormDestroy(Sender: TObject); begin UnhookMouse; end;
Testing
After compiling both projects (the dll and exe) make sure that they both in the same folder before running the exe. When the application is running, click the "Install Mouse Hook", then activate other application. Make sure our application completely covered by window from other application. Then move around the mouse. You will notice that there will be beeps sound whenever the mouse is over one of your forms, even if it's completely covered.
The zipped source codes for the demo project is attached. Would love to hear your feedback, guys!
Cheers!
Attached Files
Edited by LuthfiHakim, 12 November 2010 - 10:59 PM.