2012-03-31 9 views
10

Ich verwende eine globale Tastatur Hook-Klasse. Mit dieser Klasse kann überprüft werden, ob die Tastatur irgendwo gedrückt wurde. Und nach einiger Zeit habe ich einen Fehler:CallbackOnCollectedDelegate in globalKeyboardHook wurde erkannt

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

Hier ist globalkeyboardHook Klasse:

 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 

Irgendwelche Ideen, wie es zu beheben? Das Programm funktioniert gut, aber nach einiger Zeit friert das Programm ein und ich bekomme diesen Fehler.

+0

Versuchen Sie einen Verweis auf den Delegaten in Ihrer Klasse zu hookProc - ein tatsächliches Mitglied. Ich bin nicht sicher, ob das alles lösen wird, aber es sollte Ihr Sammlungsproblem lösen, solange Ihre Hook-Klasse noch am Leben ist. –

Antwort

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

Es ist Ihr Problem. Sie verlassen sich auf C# -Syntaxzucker, damit es automatisch ein Delegatobjekt zu hookProc erstellt. Die tatsächliche Codegenerierung sieht so aus:

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

Es gibt nur einen Verweis auf das Delegatobjekt $ temp. Aber es ist lokale Variable und verschwindet, sobald Ihre hook() -Methode die Ausführung stoppt und zurückkehrt. Der Garbage Collector ist ansonsten nicht in der Lage, zu sehen, dass Windows auch eine "Referenz" darauf hat, es kann nicht verwalteten Code für Referenzen nicht untersuchen. Wenn also der Garbage Collector das nächste Mal ausgeführt wird, wird das Delegatobjekt zerstört. Und das ist ein Kaboom, wenn Windows den Hook-Callback macht. Der integrierte MDA erkennt das Problem und generiert die hilfreiche Diagnose, bevor das Programm mit einer AccessViolation abstürzt.

Sie müssen einen zusätzlichen Verweis auf das Delegatobjekt erstellen, das lange genug überlebt. Sie könnten zum Beispiel GCHandle verwenden. Oder einfacher, speichern Sie einfach eine Referenz selbst, damit der Müllsammler immer die Referenz sehen kann. Fügen Sie Ihrer Klasse ein Feld hinzu. Zu machen ist es statisch ein sicherer Weg, um das Objekt gewährleisten kann nicht erfasst werden:

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

Keine Notwendigkeit Free pinvoke wird user32.dll immer geladen, bis Ihr Programm beendet wird.

+0

Danke für Ihre Hilfe. Jetzt klappt es perfekt :) Problem gelöst. –

+0

Wenn der User32 immer geladen ist, warum lade ich ihn explizit in Methode hook()? –

+0

Ich habe keine Zeitmaschine, um zu wissen, ob das in 20 Jahren noch stimmt. –

Verwandte Themen