2012-03-31 14 views
10

Estoy usando una clase global de hook de teclado. Esta clase permite verificar si la tecla del teclado presionó en cualquier lugar. Y después de algún tiempo estoy teniendo un error:CallbackOnCollectedDelegate en globalKeyboardHook se detectó

 **CallbackOnCollectedDelegate was detected** 

A callback was made on a garbage collected delegate of type 'Browser!Utilities.globalKeyboardHook+keyboardHookProc::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called. 

Aquí está la clase globalkeyboardHook:

 public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam); 

     public struct keyboardHookStruct 
     { 
      public int vkCode; 
      public int scanCode; 
      public int flags; 
      public int time; 
      public int dwExtraInfo; 
     } 

     const int WH_KEYBOARD_LL = 13; 
     const int WM_KEYDOWN = 0x100; 
     const int WM_KEYUP = 0x101; 
     const int WM_SYSKEYDOWN = 0x104; 
     const int WM_SYSKEYUP = 0x105; 

     public List<Keys> HookedKeys = new List<Keys>(); 

     IntPtr hhook = IntPtr.Zero; 

     public event KeyEventHandler KeyDown; 
     public event KeyEventHandler KeyUp; 

     public globalKeyboardHook() 
     { 
      hook(); 
     } 

     ~globalKeyboardHook() 
     { 
      unhook(); 
     } 

     public void hook() 
     { 
      IntPtr hInstance = LoadLibrary("User32"); 
      hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0); 
     } 

     public void unhook() 
     { 
      UnhookWindowsHookEx(hhook); 
     } 

     public int hookProc(int code, int wParam, ref keyboardHookStruct lParam) 
     { 
      if (code >= 0) 
      { 
       Keys key = (Keys)lParam.vkCode; 
       if (HookedKeys.Contains(key)) 
       { 
        KeyEventArgs kea = new KeyEventArgs(key); 
        if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null)) 
        { 
         KeyDown(this, kea); 
        } 
        else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null)) 
        { 
         KeyUp(this, kea); 
        } 
        if (kea.Handled) 
         return 1; 
       } 
      } 
      return CallNextHookEx(hhook, code, wParam, ref lParam); 
     } 

     [DllImport("user32.dll")] 
     static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId); 


     [DllImport("user32.dll")] 
     static extern bool UnhookWindowsHookEx(IntPtr hInstance); 

     [DllImport("user32.dll")] 
     static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam); 

     [DllImport("kernel32.dll")] 
     static extern IntPtr LoadLibrary(string lpFileName); 
     #endregion 

Alguna idea de cómo solucionarlo? El programa funciona bien, pero después de un tiempo el programa se congela y obtengo este error.

+0

Intente mantener una referencia al delegado en su clase para hookProc - un miembro real. No estoy seguro si eso resolverá todo, pero debería resolver su problema de colección siempre y cuando su clase de gancho aún esté activa. –

Respuesta

28
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0); 

Tiene su problema. Confía en el azúcar de sintaxis C# para que cree automáticamente un objeto delegado en hookProc. La generación de código real se ve así:

keyboardHookProc $temp = new keyboardHookProc(hookProc); 
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, $temp, hInstance, 0); 

Solo hay una referencia al objeto delegado, $ temp. Pero es una variable local y desaparece tan pronto como su método hook() deja de ejecutarse y regresa. Por lo demás, el recolector de basura no puede ver que Windows también tiene una 'referencia', ya que no puede sondear el código no administrado para buscar referencias. Entonces, la próxima vez que se ejecute el recolector de basura, el objeto delegado se destruirá. Y eso es un kaboom cuando Windows hace la devolución de llamada de enlace. El MDA incorporado detecta el problema y genera el diagnóstico útil antes de que el programa falle con AccessViolation.

Deberá crear una referencia adicional al objeto delegado que sobreviva lo suficiente. Podría usar GCHandle por ejemplo. O más fácil, simplemente almacene una referencia para que el recolector de basura siempre pueda ver la referencia. Agrega un campo a tu clase. Por lo que es estática es una manera segura de garantizar que el objeto no puede ser recogida:

private static keyboardHookProc callbackDelegate; 

    public void hook() 
    { 
     if (callbackDelegate != null) throw new InvalidOperationException("Can't hook more than once"); 
     IntPtr hInstance = LoadLibrary("User32"); 
     callbackDelegate = new keyboardHookProc(hookProc); 
     hhook = SetWindowsHookEx(WH_KEYBOARD_LL, callbackDelegate, hInstance, 0); 
     if (hhook == IntPtr.Zero) throw new Win32Exception(); 
    } 

    public void unhook() 
    { 
     if (callbackDelegate == null) return; 
     bool ok = UnhookWindowsHookEx(hhook); 
     if (!ok) throw new Win32Exception(); 
     callbackDelegate = null; 
    } 

No hay necesidad de PInvoke FreeLibrary, user32.dll se carga siempre hasta que el programa termina.

+0

Gracias por su ayuda. Ahora funciona perfectamente :) Problema resuelto. –

+0

si el User32 está siempre cargado, ¿por qué lo cargo explícitamente en el método hook()? –

+0

No tengo una máquina del tiempo para saber si todavía es cierto dentro de 20 años. –

Cuestiones relacionadas