Windows - hook de teclado

Top  Previous  Next

Programa residente em memória

 

Este exemplo simples foi testado no Windows 95/98 e tem a finalidade de ficar residente na memória. A partir do momento que este programa estiver carregado, você poderá teclar a tecla F12 em qualquer outro programa que este exemplo receberá o foco e ficará ativo.

Para testar este exemplo é muito simples. Crie um novo projeto e inclua no evento OnCreate e OnDestroy os códigos como mostrado abaixo. Feito isto basta compilar o programa e testá-lo.

As demais informações estão descritas no código abaixo.

 

unit Unit1;

interface

uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls,

        Forms, Dialogs, StdCtrls;

type

  TForm1 = class(TForm)

    Label1: TLabel;

    procedure FormCreate(Sender: TObject);

    procedure FormDestroy(Sender: TObject);

  private

    { Private declarations }

  public

    { Public declarations }

  end;

 

  function KeyboardHook(nCode: Integer; wParam: WPARAM; lParam:

      LPARAM): LResult; stdcall;

var

  Form1: TForm1;

  WinHook: HHOOK;

      { cria variável para receber handle do keyboard hook function }

implementation

{$R *.DFM}

// Inicializa a função

procedure TForm1.FormCreate(Sender: TObject);

begin

  WinHook := SetWindowsHookEx(WH_KEYBOARD, @KeyBoardHook, 00);

end;

// Remove a função

procedure TForm1.FormDestroy(Sender: TObject);

begin

  UnhookWindowsHookEx(WinHook);

end;

 

// Esta função irá detectar a tecla pressionada neste exemplo

function KeyboardHook(nCode: Integer; wParam: WPARAM; lParam:

        LPARAM): LResult;

var

  W: HWND;

begin

  if (nCode > -1) then

    if (wParam = VK_F12) then

      { Pressiona a tecla F12 }

      begin

    W := FindWindow(nil,

        ‘Residente em Memória’);

        SetForegroundWindow(W);

        ShowWindow(W, SW_RESTORE);

        Result := 1;

      end

    else

      Result := 0

  else

    Result := CallNextHookEx(WinHook, nCode, wParam, lParam);

end;

 

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

 

OTHER

 

 

Capturing keys in all Windows 

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

A lot of people ask me about the possibility that our application Delphi captures the user's keystrokes, although the user doesn't make them our active application being.   

Of course... the first thing that we make is to give a turn for the event OnKeyPress of the form, and of course, without obtaining positive results, even putting the property KeyPreview from the form to true...   

This happens because our application will only receive messages of the keystrokes when is it who has the focus. 

The following step to solve this question is fighting with the keyboard hooks.   

A Hook it is not more than a mechanism that will allow us to spy the traffic of messages between Windows and the applications.   

 

To install a hook in our application is something relatively simple, but of course, if we install it in our application, we will only spy the messages that Windows sent to our application, so neither we will have solved the problem.   

 

Then... Which is the solution?. The solution is to install a Hook but at system level, that is to say, a hook that captures all the messages that circulate toward Windows. 

 

Installing a hook at system level has a great added complication that is the fact that the function to the one that calls the hook it must be contained in a DLL, not in our Delphi application.   

This condition, will force us, in the first place to build us a DLL, and in second place to to build us some invention to communicate the DLL with our application. 

 

In this trick you have an example of keyboard capture by means of a keyboard Hook to system level.   

The example consists of two projects, one for the DLL and another for the example application.   

 

The operation is the following one:   

<li>We make DLL with two functions that we will export, one to install the hook and another for ununstall it.   

<li>There is a third function that is the one that will execute the hook once installed (CallBack). In her, that will make it is to send the data of the message captured to our application. 

 

The DLL should know in all moment the handle of the receiver application, so we will make him to read it of a memory mapped file that we will create from the own application.   

 

Well, let's go with the example: 

 

DLL that installs the Hook: 

 

Make the skeleton of a DLL (File - New - DLL)   

Change the code of the project for this another: 

 

======================================== BEGIN OF DLL ====================================

 

library Project1; 

 

Demo de Hook de teclado a nivel de sistema, Radikal. 

Como lo que queremos es capturar las teclas pulsadas en cualquier parte 

de Windows, necesitamos instalar la funcion CallBack a la que llamará 

el Hook en una DLL, que es ésta misma. 

 

uses Windows, 

  Messages; 

 

const 

CM_MANDA_TECLA = WM_USER + $1000; 

 

var 

HookDeTeclado     : HHook; 

FicheroM    : THandle; 

PReceptor   : ^Integer; 

 

function CallBackDelHook( Code    : Integer; 

                          wParam  : WPARAM; 

                          lParam  : LPARAM 

                          )       : LRESULT; stdcall; 

 

{Esta es la funcion CallBack a la cual llamará el hook.} 

{This is the CallBack function called by he Hook} 

begin 

{Si una tecla fue pulsada o liberada} 

{if a key was pressed/released} 

if code=HC_ACTION then 

begin 

  {Miramos si existe el fichero} 

  {if the mapfile exists} 

  FicheroM:=OpenFileMapping(FILE_MAP_READ,False,'ElReceptor'); 

  {Si no existe, no enviamos nada a la aplicacion receptora} 

  {If dont, send nothing to receiver application} 

  if FicheroM<>0 then 

  begin 

    PReceptor:=MapViewOfFile(FicheroM,FILE_MAP_READ,0,0,0); 

    PostMessage(PReceptor^,CM_MANDA_TECLA,wParam,lParam); 

    UnmapViewOfFile(PReceptor); 

    CloseHandle(FicheroM); 

  end; 

end; 

{Llamamos al siguiente hook de teclado de la cadena} 

{call to next hook of the chain} 

Result := CallNextHookEx(HookDeTeclado, Code, wParam, lParam) 

end; 

 

procedure HookOn; stdcall; 

{Procedure que instala el hook} 

{procedure for install the hook} 

begin 

  HookDeTeclado:=SetWindowsHookEx(WH_KEYBOARD, @CallBackDelHook, HInstance , 0); 

end; 

 

procedure HookOff;  stdcall; 

begin 

{procedure para desinstalar el hook} 

{procedure to uninstall the hook} 

  UnhookWindowsHookEx(HookDeTeclado); 

end; 

 

exports 

{Exportamos las procedures...} 

{Export the procedures} 

HookOn, 

HookOff; 

 

begin 

end. 

 

=========================================== END OF DLL =========================================

 

Now record the project with the name: ' HookTeclado.dpr' and compile it (Project - Build All), and you will have generated the DLL of the project. 

 

Receiver application 

 

<li>Make a new empty application 

<li>Put a TMemo (Memo1) in the form 

<li>Change the form's unit code by this other: 

 

======================================== BEGIN OF PROGRAM ======================================

 

unit Unit1; 

 

interface 

 

uses 

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 

  StdCtrls; 

 

const 

  NombreDLL       = 'HookTeclado.dll'

  CM_MANDA_TECLA  = WM_USER + $1000

 

 

type 

  THookTeclado=procedure; stdcall; 

 

type 

  TForm1 = class(TForm) 

    Label1: TLabel; 

    Memo1: TMemo; 

    Button1: TButton; 

    procedure FormCreate(Sender: TObject); 

    procedure FormDestroy(Sender: TObject); 

  private 

    { Private declarations } 

    FicheroM       : THandle; 

    PReceptor      : ^Integer; 

    HandleDLL      : THandle; 

    HookOn, 

    HookOff        : THookTeclado; 

 

    procedure LlegaDelHook(var message: TMessage); message  CM_MANDA_TECLA; 

  public 

    { Public declarations } 

  end

 

var 

  Form1: TForm1; 

 

implementation 

 

{$R *.DFM} 

 

procedure TForm1.FormCreate(Sender: TObject); 

begin 

  {No queremos que el Memo maneje el teclado...} 

  {We dont want that the memo read the keyboard...} 

  Memo1.ReadOnly:=TRUE; 

 

  HandleDLL:=LoadLibrary( PChar(ExtractFilePath(Application.Exename)+ 

                                NombreDLL ) ); 

  if HandleDLL = 0 then raise Exception.Create('No se pudo cargar la DLL'); 

 

  @HookOn :=GetProcAddress(HandleDLL, 'HookOn'); 

  @HookOff:=GetProcAddress(HandleDLL, 'HookOff'); 

 

  IF not assigned(HookOn) or 

     not assigned(HookOff)  then 

     raise Exception.Create('No se encontraron las funciones en la DLL'+#13

                            'Cannot find the required DLL functions'); 

 

  {Creamos el fichero de memoria} 

  FicheroM:=CreateFileMapping( $FFFFFFFF

                              nil, 

                              PAGE_READWRITE, 

                              0

                              SizeOf(Integer), 

                              'ElReceptor'); 

 

   {Si no se creó el fichero, error} 

   if FicheroM=0 then 

     raise Exception.Create'Error al crear el fichero'

                             '/Error while create file'); 

 

   {Direccionamos nuestra estructura al fichero de memoria} 

   PReceptor:=MapViewOfFile(FicheroM,FILE_MAP_WRITE,0,0,0); 

 

   {Escribimos datos en el fichero de memoria} 

   PReceptor^:=Handle; 

   HookOn; 

end

 

procedure TForm1.LlegaDelHook(var message: TMessage); 

var 

   NombreTecla : array[0..100of char; 

   Accion      : string; 

begin 

  {Traducimos de Virtual key Code a TEXTO} 

  {Virtual key code to Key Name} 

  GetKeyNameText(Message.LParam,@NombreTecla,100); 

 

  {Miramos si la tecla fué pulsada, soltada o repetida} 

  {Look if the key was pressed, released o re-pressed} 

  if ((Message.lParam shr 31and 1)=1 

      then Accion:='Soltada' {Released} 

  else 

  if ((Message.lParam shr 30and 1)=1 

      then Accion:='Repetida' {repressed} 

      else Accion:='Pulsada'{pressed} 

 

  Memo1.Lines.Append( Accion+ 

                      ' tecla: '

                      String(NombreTecla) ); 

end

 

procedure TForm1.FormDestroy(Sender: TObject); 

begin 

{Desactivamos el Hook} 

{Uninstall the Hook} 

if Assigned(HookOff) then HookOff; 

 

{Liberamos la DLL} 

{Free the DLL} 

if HandleDLL<>0 then 

  FreeLibrary(HandleDLL); 

 

{Cerramos la vista del fichero y el fichero} 

{Close the memfile and the View} 

if FicheroM<>0 then 

begin 

   UnmapViewOfFile(PReceptor); 

   CloseHandle(FicheroM); 

end

 

end

 

end

 

====================================== END OF PROGRAM =======================================

 

<li>Record the project in the same directory of the project of the DLL and compile the application. 

 

If you have followed the steps until here, you will have in the directory of the two projects a DLL (HookTeclado.DLL) and the executable of the receiver application.   

Execute it, and you will see in the Memo1 all the keys pressed in Windows. 

 

If you only wanted an example that works, it is not necessary that you continue reading. If you want to know a little more than like the invention works... so... here you have it, step to step: 

 

We go starting from the event OnCreate of the application: 

 

First, we put the Memo1 to readonly. Imagine for what reason, or better, it proves to not putting it, to see that it happens...:) 

 

procedure TForm1.FormCreate(Sender: TObject); 

begin 

  {No queremos que el Memo maneje el teclado...} 

  {We dont want that the memo read the keyboard...} 

  Memo1.ReadOnly:=TRUE; 

 

Now we load the DLL that we will suppose that it will be in the same directory that our executable one. If there was some problem when loading it, we generate an exception, in such a way that the following code would not be executed. 

 

  HandleDLL:=LoadLibrary( PChar(ExtractFilePath(Application.Exename)+ 

                                NombreDLL ) ); 

  if HandleDLL = 0 then raise Exception.Create('No se pudo cargar la DLL'); 

 

<li>Once loaded the DLL, we look for the two functions that they should be in it. If they are not... we generate an exception. 

 

  @HookOn :=GetProcAddress(HandleDLL, 'HookOn'); 

  @HookOff:=GetProcAddress(HandleDLL, 'HookOff'); 

 

  IF not assigned(HookOn) or 

     not assigned(HookOff)  then 

     raise Exception.Create('No se encontraron las funciones en la DLL'+#13

                            'Cannot find the required DLL functions'); 

 

<li>Now, we make a memory mapped file, which will use to keep the handle of our form, the DLL will taste this way like who must send him the message with the key that has been pressed just  reading this file. 

 

  {Creamos el fichero de memoria} 

  FicheroM:=CreateFileMapping( $FFFFFFFF

                              nil, 

                              PAGE_READWRITE, 

                              0

                              SizeOf(Integer), 

                              'ElReceptor'); 

 

   {Si no se creó el fichero, error} 

   if FicheroM=0 then 

     raise Exception.Create'Error al crear el fichero'

                             '/Error while create file'); 

 

   {Direccionamos nuestra estructura al fichero de memoria} 

   PReceptor:=MapViewOfFile(FicheroM,FILE_MAP_WRITE,0,0,0); 

 

<li>Once we have the memory mapped file, and a view pointing to it, we record the handle of the form in it, and we activate the Hook, calling to the procedure HookOn of the DLL: 

 

   {Escribimos datos en el fichero de memoria} 

   PReceptor^:=Handle; 

   HookOn; 

end

 

<li>Well, now see that it happens in our DLL when calling to the function HookOn: 

 

procedure HookOn; stdcall; 

{Procedure que instala el hook} 

{procedure for install the hook} 

begin 

  HookDeTeclado:=SetWindowsHookEx(WH_KEYBOARD, @CallBackDelHook, HInstance , 0); 

end

 

As you see, there is not more than a call to SetWindowsHookEx, to install a hook at system level (0 in the last parameter) that will execute the function CallBackDelHook with each message that it captures. 

 

<li>Let us see that it makes the function CallBackDelHook when it is executed by the hook: 

 

First, it checks that the function has been called by a new keyboard event, by means of the if code=HC_ACTION. 

 

function CallBackDelHook( Code    : Integer; 

                          wParam  : WPARAM; 

                          lParam  : LPARAM 

                          )       : LRESULT; stdcall; 

 

{Esta es la funcion CallBack a la cual llamará el hook.} 

{This is the CallBack function called by he Hook} 

begin 

{Si una tecla fue pulsada o liberada} 

{if a key was pressed/released} 

if code=HC_ACTION then 

begin 

 

If it is this way, that is to say that is a new keyboard event that it is necessary to assist... the first thing that we should make is so to look for the handle from the application to which should send the message with the data of the pressed/released key, which have kept by heart in a file from the application, we try to open the file, and to read this handle, and if everything goes well, we send the message by means of a PostMessage: 

 

  {Miramos si existe el fichero} 

  {if the mapfile exists} 

  FicheroM:=OpenFileMapping(FILE_MAP_READ,False,'ElReceptor'); 

  {Si no existe, no enviamos nada a la aplicacion receptora} 

  {If dont, send nothing to receiver application} 

  if FicheroM<>0 then 

  begin 

    PReceptor:=MapViewOfFile(FicheroM,FILE_MAP_READ,0,0,0); 

    PostMessage(PReceptor^,CM_MANDA_TECLA,wParam,lParam); 

 

once sent the message, we free the file: 

 

    UnmapViewOfFile(PReceptor); 

    CloseHandle(FicheroM); 

  end

end

 

later, should call to next hook: 

 

{Llamamos al siguiente hook de teclado de la cadena} 

{call to next hook of the chain} 

Result := CallNextHookEx(HookDeTeclado, Code, wParam, lParam) 

end

 

Well, do we have installed a hook that captures the keyboard events and does it forward them to our application... which the following step is?, of course... to make something to receive it...   

We will have to capture user's message that we have been defined: 

 

const 

CM_MANDA_TECLA = WM_USER + $1000; 

 

that which we will get adding this line in the private part of the form: 

 

procedure LlegaDelHook(var message: TMessage); message  CM_MANDA_TECLA; 

 

and of course, the corresponding procedure in the implementation part: 

 

procedure TForm1.LlegaDelHook(var message: TMessage); 

var 

   NombreTecla : array[0..100] of char; 

   Accion      : string; 

begin 

  {Traducimos de Virtual key Code a TEXTO} 

  {Virtual key code to Key Name} 

  GetKeyNameText(Message.LParam,@NombreTecla,100); 

 

  {Miramos si la tecla fué pulsada, soltada o repetida} 

  {Look if the key was pressed, released o re-pressed} 

  if ((Message.lParam shr 31) and 1)=1 

      then Accion:='Soltada' {Released} 

  else 

  if ((Message.lParam shr 30) and 1)=1 

      then Accion:='Repetida' {repressed} 

      else Accion:='Pulsada'; {pressed} 

 

  Memo1.Lines.Append( Accion+ 

                      ' tecla: '+ 

                      String(NombreTecla) ); 

end; 

 

In this example, I simply translate the data of the pressed/released key, translating it to its key name and adding it to the TMemo.   

If you want more information on the parameters than the function will receive, revise the help file Win32.hlp looking for the topic ' KeyboardProc'.   

There you will see the meaning of the parameters wParam and lParam that you will receive in the function. 

 

For I finish, we have left to undo this whole invention when we leave the application. The OnDestroy event of the application: 

 

First, uninstall the hook, calling to the HookOff function of the DLL. Care, is to use the if Assigned, because if there has been some problem when loading the DLL in the OnCreate... now we would try to execute something that was not initialized. 

 

procedure TForm1.FormDestroy(Sender: TObject); 

begin 

{Desactivamos el Hook} 

{Uninstall the Hook} 

if Assigned(HookOff) then HookOff; 

 

Now, free the DLL: 

 

{Liberamos la DLL} 

{Free the DLL} 

if HandleDLL<>0 then 

  FreeLibrary(HandleDLL); 

 

And the file: 

 

{Cerramos la vista del fichero y el fichero} 

{Close the memfile and the View} 

if FicheroM<>0 then 

begin 

   UnmapViewOfFile(PReceptor); 

   CloseHandle(FicheroM); 

end; 

end;