Cuando instales ganchos, recuerda que ellos afectan el sistema de desempeño. Los ganchos de ancho de sistema [system-wide] son los más notorios en este aspecto. Como TODOS los eventos relacionados serán dirigidos a través de tu función filtro, tu sistema puede ralentizarse un poco. Así que si quieres usar un gancho de ancho de sistema, deberías usarlo juiciosamente y desactivarlo tan pronto ya no lo necesites. También tienes una probabilidad más alta de quebrar [crashing] los otros procesos, ya que puedes mediar [meddle] con otros procesos y si algo va mal en tu función filtro, se pueden derribar los otros procesos y lanzarlos al olvido. Recuerda: el poder viene con responsabilidades.
Tienes que entender como trabaja un gancho antes de que puedas usarlo con eficiencia. Cuando creas un gancho, Windows crea una estructura de datos en la memoria, que contiene información sobre el gancho, y lo agrega a una lista enlazada de ganchos existentes. El gancho nuevo es agregado en frente de los ganchos antiguos. Cuando un evento ocurre, si instalas un gancho local, la función filtro en tu proceso es llamada de una manera directa. Pero si es un gancho remoto, el sistema debe inyectar el código para el procedimiento del gancho dentro del espacio de direcciones del(os) otro(s) proceso(s). Y el sistema puede hacer eso sólo si la función reside en una DLL. De esta manera, si quieres usar un gancho remoto, tu procedimiento de gancho debe residir en una DLL. Hay dos excepciones a esta regla: ganchos de grabación diaria [journal record] y ganchos de ejecución diaria [journal playback]. El procedimiento de gancho para estos dos ganchos debe residir en el hilo que instala los ganchos. La razón por la que debe ser así, es porque ambos ganchos tienen que ver con la intercepción de bajo-nivel de los eventos de entrada del hardware. Los eventos de entrada deben ser grabados/ejecutados [recorded/playbacked] en el orden que aparecen. Si el código de estos dos ganchos está en un DLL, los eventos de entrada pueden dispersarse entre varios hilos y es imposible saber su orden. Así que la solución es: el procedimiento de gancho de esos dos gancho deben estar solamente en un hilo, en el hilo que instala los ganchos.
Hay 14 tipos de ganchos:
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORDSi la llamada tiene éxito, regresa el manejador de gancho en eax. Si no, regresa NULL. Debes salvar el manejador del gancho para poder desinstalarlo después.
- HookType es uno de los valores en la lista de arriba, e.g., WH_MOUSE, WH_KEYBOARD
- pHookProc es la dirección del procedimiento de gancho que será llamada para procesar los mensajes para el gancho específico. Si el gancho es remoto, debe residir en una DLL. Si no, debe estar en tu proceso.
- hInstance es el manejador de instacia de la DLL en la cual reside el procedimiento de gancho. Si el gancho es local, este valor debe ser NULL
- ThreadID es el ID del hilo para el cual quieres instalar el gancho que lo espíe. Este parámetro es el que determina si el gancho es local o remoto. Si este parámetro es NULL, Windows interpretará el gancho como un gancho remoto de ancho de sistema [system-wide] que afecta todos los hilos del sistema. Si quieres especificar el ID de un hilo en tu propio proceso, este hilo es local. Si especificas el ID del hilo de otro proceso, el gancho es thread-specific remote one. Hay dos excepciones a esta regla: WH_JOURNALRECORD y WH_JOURNALPLAYBACK siempre son ganchos de ancho de sistema [system-wide] locales que no se requieren que estén en una DLL. Y WH_SYSMSGFILTER siempre es un gancho remoto de ancho de sistema [system-wide]. Realmente es idéntica al gancho WH_MSGFILTER con ThreadID==0.
Puedes desinstalar un gancho llamando a UnhookWindowsHookEx que sólo acepta un parámetro, el manejador del gancho que quieres desinstalar. Si la llamada tiene éxtito, regresa un valor diferente a cero en eax. En caso contrario, regresa NULL.
Ahora que sabes cómo instalar/desinstalar
ganchos, podemos examinar el procedimiento de gancho.
El procedimiento de gancho será
llamado cada vez que ocurre un evento asociado con el tipo de gancho que haz
instalado. Por ejemplo, si instalas el gancho WH_MOUSE,
cuando ocurre un evento de ratón, será llamado tu procedimiento
de gancho. Independientemente del tipo de gancho que instalaste, el procedimiento
de gancho siempre tiene el siguiente prototipo:
WH_CALLWNDPROCWH_MOUSE
- nCode sólo puede ser HC_ACTION lo cual significa que hay un mensaje enviado a la ventana
- wParam contiene el mensaje que está siendo enviado, si no lo tiene es cero
- lParam apunta a una estructura CWPSTRUCT
- return value: no se usa, regresa cero
- nCode puede ser HC_ACTION o HC_NOREMOVE
- wParam contiene el mensaje del ratón
- lParam apunta a una estructura MOUSEHOOKSTRUCT
- return value: cero si el mensaje debería ser procesado. 1 si el mensaje deberúia ser descartado.
La línea de abajo es: debes consultar tu referencia de la api de win32 para detalles sobre los significados de los parámetros y regresar un valor al gancho que quieres instalar.
Ahora hay un poco de catch sobre el procedimiento de gancho. Recuerda que los ganchos estan encadenados en una lista enlazada con el gancho instalado más recientemente colocado en la cabeza de la lista. Cuando ocurre un evento, Windows llamará sólo al primer gancho de la cadena. Es responsabilidad de tu procedimiento de gancho llamar al siguiente gancho en la cadena. Tú no eliges llamar al siguiente gancho, pero mejor deberías saber qué estás haciendo. Muchas veces, es una buena práctica llamar al siguiente procedimiento de manera que otros ganchos puedan tener una impresión [shot] del evento. Puedes invocar al siguiente gancho llamando a CallNextHookEx que tiene el siguiente prototipo:
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORDUna nota importante sobre los ganchos remotos: el procedimiento de gancho debe residir en una DLL que será proyectada dentro de otro proceso. Cuando Windows proyecta la DLL dentro de otros procesos, no proyectará la(s) sección(es) de datos(s) dentro de otros procesos. En pocas palabras, todos los procesos comparten una copia sencilla del código, ¡pero ellos tendrán su propia copia privada de la sección de datos de la DLL! Esto puede resultar una gran sorpresa para el incuto. Puedes pensar que cuando almacenas un valor dentro de una variable en la sección de datos de una DLL, ese valor será compartido entre todos los procesos que cargan la DLL dentro de su espacio de direcciones. No es tan cierto. En una situación normal, esta conducta es deseable ya que provee la ilusión de que cada proceso tiene su propia copia de la DLL. Pero no cuando hay involucrados ganchos de Windows. Queremos que la DLL sea idéntica en todos los procesos, incluyendo los datos. La solución: debes marcar la sección de datos como compartida. Puedes hacer esto especificando el atributo de la(s) sección(es) en el conmutador [switch] del enlazador [linker]. Para MASM, necesitas usar este conmutador:
- hHook es tu propio manejador de gancho. La función usa este manejador para atravesar la lista enlazada y buscar el siguiente procedimiento de gancho que debería ser llamado.
- nCode, wParam y lParam puedes pasar estos tres valores que recibes de Windows a CallNextHookEx.
/SECTION:<section name>, SEl nombre de la sección de datos inicializada es .data y el de la de los datos no-inicializados es .bss. Por ejemplo, si quieres ensamblar una DLL que contiene un procedimiento de gancho y quieres que la sección de datos no inicialoizados sea compartida entre procesos, debes usar la siguiente línea:
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........El atributo S marca la sección como compartida.
;---------------------------------------------
Este es el código fuente del programa principal -----------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include
mousehook.inc
includelib
mousehook.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>
.const
IDD_MAINDLG
equ 101
IDC_CLASSNAME
equ 1000
IDC_HANDLE
equ 1001
IDC_WNDPROC
equ 1002
IDC_HOOK
equ 1004
IDC_EXIT
equ 1005
WM_MOUSEHOOK
equ WM_USER+6
DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
HookFlag dd FALSE
HookText db "&Hook",0
UnhookText db "&Unhook",0
template db "%lx",0
.data?
hInstance dd ?
hHook dd ?
.code
start:
invoke
GetModuleHandle,NULL
mov
hInstance,eax
invoke
DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL
invoke
ExitProcess,NULL
DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL
hLib:DWORD
LOCAL
buffer[128]:byte
LOCAL
buffer1[128]:byte
LOCAL
rect:RECT
.if
uMsg==WM_CLOSE
.if HookFlag==TRUE
invoke UninstallHook
.endif
invoke EndDialog,hDlg,NULL
.elseif
uMsg==WM_INITDIALOG
invoke GetWindowRect,hDlg,addr rect
invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right,
rect.bottom, SWP_SHOWWINDOW
.elseif
uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
.elseif
uMsg==WM_COMMAND
.if lParam!=0
mov eax,wParam
mov edx,eax
shr edx,16
.if dx==BN_CLICKED
.if ax==IDC_EXIT
invoke SendMessage,hDlg,WM_CLOSE,0,0
.else
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
.else
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
.endif
.endif
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov
eax,TRUE
ret
DlgFunc endp
end start
;-----------------------------------------------------
Este es el código fuente de la DLL --------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.const
WM_MOUSEHOOK equ WM_USER+6
.data
hInstance dd 0
.data?
hHook dd ?
hWnd dd ?
.code
DllEntry proc hInst:HINSTANCE,
reason:DWORD, reserved1:DWORD
.if
reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
mov
eax,TRUE
ret
DllEntry Endp
MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke
CallNextHookEx,hHook,nCode,wParam,lParam
mov
edx,lParam
assume
edx:PTR MOUSEHOOKSTRUCT
invoke
WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke
PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume
edx:nothing
xor
eax,eax
ret
MouseProc endp
InstallHook proc hwnd:DWORD
push
hwnd
pop
hWnd
invoke
SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov
hHook,eax
ret
InstallHook endp
UninstallHook proc
invoke
UnhookWindowsHookEx,hHook
ret
UninstallHook endp
End DllEntry
;---------------------------------------------- Est es el makefile de la DLL ----------------------------------------------
NAME=mousehook
$(NAME).dll: $(NAME).obj
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib
$(NAME).obj
$(NAME).obj: $(NAME).asm
ml /c /coff /Cp $(NAME).asm
El ejemplo desplegará una caja de diálogo [dialog box] con tres controles de edición que serán llenados con el nombre de la clase, el manejador [handle] de ventana y la dirección del procedimiento de ventana asociada con la ventana bajo el cursor del ratón. Hay dos botones, Hook (gancho) y Exit (Salir). Cuando presionas el botón Hook, el programa engancha la entrada del ratón y el texto en el botón cambia a Unhook (desenganchar). Cuando mueves el cursor del ratón sobre la ventana, la info acerca de esa ventana será desplegada en la ventana principal del ejemplo. Cuando presionas el botón Unhook, el programa remueve el ganchjo del ratón.
El programa principal usa una caja de diálogo [dialog box] como su ventana principal. Define un mensaje hecho a la medida [custom message], WM_MOUSEHOOK que será usado entre el programa principal y la DLL de gancho. Cuando el programa principal recibe este mensaje, wParam contiene el manejador de la ventana sobre la cual está el cursor del ratón. Por supuesto, este es un plan arbitrario. Yo decido enviar un manejador en wParam por razones de simplicidad. Tú puedes escoger tu propio método de comunicación entre la ventana principal y la DLL de gancho.
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
El programa mantiene una bandera, HookFlag, para monitorear el estado del gancho. Es FALSE si no se instala el gancho y TRUE si el gancho es instalado.
Cuando el usuario presiona el botón Hook, el programa chequea si el gancho ya está instalado. Si no lo está, se llama a la función InstallHook en la DLL de gancho para instalarlo. Nota que pasamos el manejador de la ventana principal como parámetro de la función de manera que la DLL de gancho pueda enviar mensajes WM_MOUSEHOOK a la ventana correcta,es decir la tuya propia.
Cuando el programa es cargado, la DLL de gancho tabmbién es cargada. Realmente, las DLLs son cargadas inmediatamente después de que el programa está en memoria. El punto de entrada de la DLL es llamado incluso antes de que se ejecute la primera instrucción del programa principal. Así que cuando el programa principal ejecuta la(s) DLL(s) es/son inicializada(s). Ponemos el siguiente código en el punto de entrada de la DLL de gancho:
.if
reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
El código salva el manejador de instancia de la DLL de gancho misma a una variable global llamada hInstance para usar dentro de la función InstallHook. Ya que la función del punto de entrada de la DLL es llamada antes de que sean llamadas otras funciones de la DLL, hInstance siempre es válido. Ponemos hInstance en la sección .data, así que este valor es guardado en la base de la sección por proceso [is kept on per-process basis]. Debido a que cuando el cursor del ratón pasa sobre una ventana, la DLL de gancho es proyectada en el proceso. Imagina que ya hay una DLL que ocupa las direcciones de la DLL de gancho que se intentó cargar, la DLL de gancho debería ser re-proyectada a otra dirección. El valor de hInstance será actualizado para las de las nuevas direcciones cargadas. Cuando el usuario presiona el botón Unhook y luego el botón Hook, SetWindowsHookEx será llamada de nuevo. Sin embargo, esta vez, se usará el nuevo espacio de direcciones cargado como el manejador de instacia lo cual será erróneo porque en este proceso ejemplo la dirección de carga de la DLL de gancho no ha sido cambiada. El gancho será local donde puedes enganchar sólo los eventos de ratón que ocurren en tu propia ventana. Difícilmente deseable [hardly desirable].
InstallHook proc hwnd:DWORD
push
hwnd
pop
hWnd
invoke
SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov
hHook,eax
ret
InstallHook endp
La función InstallHook es muy simple. Salva el manejador de ventana pasado como su parámetro a una variable global llamada hWnd para ser usada luego. Luego llama a SetWindowsHookEx para instalar un gancho de ratón. El valor de retorno de SetWindowsHookEx es almacenado en una variable global llamada hHook para usar con UnhookWindowsHookEx.
Después de que es llamada SetWindowsHookEx, el gancho del ratón es funcional. Cada vez que ocurre un evento de ratón del sistema, es llamada MouseProc (tu procedimiento de ventana).
MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke
CallNextHookEx,hHook,nCode,wParam,lParam
mov
edx,lParam
assume
edx:PTR MOUSEHOOKSTRUCT
invoke
WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke
PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume
edx:nothing
xor
eax,eax
ret
MouseProc endp
Lo primero que hace es llamar a CallNextHookEx para dar a otros ganchos el chance de procesar el evento del ratón. Después de eso, llma a la función WindowFromPoint para regresar el manejador de la ventana en la coordenada especificada del monitor. Nota que usamos la estructura POINT en la estructura MOUSEHOOKSTRUCT apuntada por lParam como la coordenada actual del ratón. Después de que enviamos el manejador de ventana a la ventana principal a través de PostMessage con el mensaje WM_MOUSEHOOK. Algo que deberías recordar es que: no deberías usar SendMessage dentro del procedimiento de gancho, ya que puede causar estancamiento de mensajes. Es más recomendable PostMessage. La estructura MOUSEHOOKSTRUCT se define abajo:
MOUSEHOOKSTRUCT STRUCT
DWORD
pt
POINT <>
hwnd
DWORD ?
wHitTestCode
DWORD ?
dwExtraInfo
DWORD ?
MOUSEHOOKSTRUCT ENDS
.elseif
uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
Para evitar parpadeos [flickers],
chequeamos el texto que está todavía en los controles de edición y el texto
que se pondrá dentro de ellos para comprobar si on idénticos. Si lo son, los
saltamos.
Regresamos el nombre de la clase llamando a GetClassName, la dirección del procedimiento de ventana
llamando a GetClassLong con GCL_WNDPROC y luego los formateamos dentro de cadenas y los ponemos dentro de los controles de edición apropiados.
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
Cuando el usuario presiona el botón Unhook, el programa llama a la función UninstallHook en la DLL de gancho. UninstallHook llama a UnhookWindowsHookEx. Después de eso, cambia el texto del botón una vez más a "Hook", HookFlag a FALSE y se limpia el contenido de los controles de edición.
Nota que el conmutador [switch] del enlazador en el makefile.
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS
Especifica a la sección .bss como una sección compartida para hacer que todos los procesos compartan la sección de datos no inicializados de la DLL de gancho. Sin este conmutador [switch], tu DLL de gancho no funcionará correctamente.
[Iczelion's Win32 Assembly HomePage]
n u M I T_o r's Programming Page
Este tutorial, original de Iczelion, ha sido traducido por: n u M I T_o r