Mucha gente me pregunta acerca de la posibilidad de que nuestra aplicacion Delphi capture las pulsaciones de teclas del usuario, aunque el usuario no las haga estando nuestra aplicacion activa.
Por supuesto... lo primero que hacemos es dar una vuelta por el evento OnKeyPress de la form, y claro, sin obtener resultados positivos, incluso poniendo la propiedad KeyPreview de la form a true...
Esto ocurre porque nuestra aplicación sólo recibirá mensajes de pulsaciones de teclado cuando es ella quien tiene el foco.
El siguiente paso para resolver este 'reto' es la de pelearse con los hooks de teclado.
Un Hook (en español algo así como 'gancho') no es más que un mecanismo que nos permitirá espiar el tráfico de mensajes entre Windows y las aplicaciones.
Instalar un hook en nuestra aplicación es algo relativamente sencillo, pero claro, si lo instalamos en nuestra aplicación, tan sólo 'espiaremos' los mensajes que Windows envie a nuestra aplicacion, con lo que tampoco habremos resuelto el problema.
¿Cual es la solución entonces?, pues la solución pasa por instalar un Hook pero a nivel de sistema, es decir, un 'gancho' que capture todos los mensajes que circulen hacia Windows.
El instalar un hook a nivel de sistema tiene una gran complicación añadida, que es el hecho de que la función a la que llama el hook ha de estar contenida en una DLL, no en nuestra aplicación Delphi.
Esta condición, nos obligará, en primer lugar a construirnos una DLL, y en segundo lugar a construirnos algun invento para comunicar la DLL con nuestra aplicación.
En este truco tienes un ejemplo de captura de teclado mediante un Hook de teclado a nivel de sistema.
El ejemplo consta de dos proyectos, uno para la DLL y otro para la aplicación de ejemplo.
El funcionamiento es el siguiente:
Creamos una DLL con dos funciones que exportaremos, una para instalar el hook y otra para desinstalarlo. Hay una tercera funcion que es la que ejecutará el hook una vez instalado (CallBack). En ella, lo que haremos es enviar los datos del mensaje capturado a nuestra aplicacion.
La DLL debe saber en todo momento el handle de la aplicacion receptora, así que haremos que lo lea de un fichero mapeado en memoria que crearemos desde la propia aplicacion. Enviaremos los datos desde la DLL a la aplicacion a través de un mensaje de usuario.
Bien, vamos con el ejemplo:
La DLL que instala el Hook:
Crea el esqueleto de una DLL (File - New - DLL)
Cambia el código del proyecto por éste otro:
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;
{Esta es la funcion CallBack a la cual llamará el hook.}
function CallBackDelHook( Code : Integer;
wParam : WPARAM;
lParam : LPARAM
) : LRESULT; stdcall;
begin
{Si una tecla fue pulsada o liberada}
if code=HC_ACTION then
begin
{Miramos si existe el fichero}
FicheroM:=OpenFileMapping(FILE_MAP_READ,False,'ElReceptor');
{Si no existe, no enviamos nada a la aplicacion receptora}
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}
Result := CallNextHookEx(HookDeTeclado, Code, wParam, lParam)
end;
{Procedure que instala el hook}
procedure HookOn; stdcall;
begin
HookDeTeclado:=SetWindowsHookEx(WH_KEYBOARD, @CallBackDelHook, HInstance , 0);
end;
{procedure para desinstalar el hook}
procedure HookOff; stdcall;
begin
UnhookWindowsHookEx(HookDeTeclado);
end;
{Exportamos las procedures...}
exports
HookOn,
HookOff;
begin
end.
Ahora graba el proyecto con el nombre: 'HookTeclado.dpr' y la compilas (Project - Build All), y habrás generado la DLL del proyecto.
Aplicacion que recibe datos del Hook
Crea una nueva aplicacion y pon un TMemo (Memo1) en el form. Cambia el código de la unit de la form por éste otro:
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...}
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..100] of char;
Accion : string;
begin
{Traducimos de Virtual key Code a TEXTO}
GetKeyNameText(Message.LParam,@NombreTecla,100);
{Miramos si la tecla fué pulsada, soltada o repetida}
if ((Message.lParam shr 31) and 1)=1 then
Accion:='Soltada' {Released}
else
if ((Message.lParam shr 30) and 1)=1 then
Accion:='Repetida'
else
Accion:='Pulsada';
Memo1.Lines.Append(Accion+' tecla: '+String(NombreTecla));
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
{Desactivamos el Hook}
if Assigned(HookOff) then HookOff;
{Liberamos la DLL}
if HandleDLL<>0 then
FreeLibrary(HandleDLL);
{Cerramos la vista del fichero y el fichero}
if FicheroM<>0 then
begin
UnmapViewOfFile(PReceptor);
CloseHandle(FicheroM);
end;
end;
end.
Graba el proyecto en el mismo directorio del proyecto de la DLL y compila la aplicación.
Si has seguido los pasos hasta aqui, deberás tener en el directorio de los dos proyectos una DLL (HookTeclado.DLL) y el ejecutable de la aplicacion receptora. Ejecutalo, y verás como en el Memo1 irán apareciendo todas las teclas que pulses en Windows.
|