CallbackOnCollectedDelegate em globalKeyboardHook foi detectado

Eu estou usando uma class de gancho de teclado global. Esta class permite verificar se a tecla do teclado é pressionada em qualquer lugar. E depois de algum tempo estou com um erro:

**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. 

Aqui está a class 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 HookedKeys = new List(); 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 

Alguma idéia de como consertar isso? O programa funciona bem, mas depois de algum tempo o programa congela e eu recebo esse erro.

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

Esse é o seu problema. Você está contando com o açúcar de syntax C # para que ele crie automaticamente um object delegado para hookProc . Geração de código real se parece com isso:

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

Há apenas uma referência ao object delegado, $ temp. Mas é variável local e desaparece assim que o método hook () pára de executar e retorna. O coletor de lixo é, de outra forma, impotente para ver que o Windows também tem uma ‘referência’ para ele, ele não pode sondar código não gerenciado para referências. Então, da próxima vez que o coletor de lixo for executado, o object delegado será destruído. E isso é um kaboom quando o Windows faz o retorno de chamada do gancho. O MDA incorporado detecta o problema e gera o diagnóstico útil antes que o programa trave com um AccessViolation.

Você precisará criar uma referência adicional ao object delegado que sobrevive por tempo suficiente. Você poderia usar o GCHandle por exemplo. Ou mais fácil, basta armazenar um referencial para que o coletor de lixo possa sempre ver a referência. Adicione um campo à sua turma. Torná-lo estático é uma maneira infalível de garantir que o object não possa ser coletado:

  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; } 

Não há necessidade de pinvoke FreeLibrary, user32.dll é sempre carregado até o seu programa termina.