2012-03-31 17 views
10

Używam globalnej klasy hakowania klawiatury. Ta klasa pozwala sprawdzić, czy klawisz klawiatury jest wciśnięty w dowolnym miejscu. I po jakimś czasie mam błąd:CallbackOnCollectedDelegate w globalKeyboardHook został wykryty

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

Oto globalkeyboardHook klasa:

 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 

Jakieś pomysły jak to naprawić? Program działa dobrze, ale po pewnym czasie program zawiesza się i pojawia się ten błąd.

+0

Spróbuj odwołać się do delegata w swojej klasie do hookProc - rzeczywistego członka. Nie jestem pewien, czy to wszystko rozwiąże, ale powinno rozwiązać problem z kolekcją, o ile twoja klasa haczyków wciąż żyje. –

Odpowiedz

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

Twój problem. Bazuje na składni cukru C#, aby automatycznie utworzyć obiekt uczestnika do hookProc. Rzeczywiste generowanie kodu wygląda następująco:

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

Jest tylko jedno odniesienie do obiektu uczestnika, $ temp. Ale jest zmienną lokalną i znika, gdy tylko twoja metoda hook() przestaje działać i wraca. Śmieciarz jest inaczej bezsilny, widząc, że Windows ma "odniesienie" do niego, a także nie może sondować niezarządzanego kodu dla odniesień. Następnym razem, gdy uruchomi się garbage collector, obiekt uczestnika zostanie zniszczony. A to kaboom, gdy Windows wykonuje wywołanie zwrotne haka. Wbudowany MDA wykrywa problem i generuje przydatne informacje diagnostyczne, zanim program ulegnie awarii wraz z naruszeniem dostępu.

Będziesz musiał utworzyć dodatkowe odniesienie do obiektu uczestnika, który przetrwa wystarczająco długo. Możesz użyć GCHandle na przykład. Lub łatwiej, po prostu przechowuj referencje, aby garbage collector mógł zawsze zobaczyć referencję. Dodaj pole do swojej klasy. Czyni go statyczny to najpewniejszy sposób, aby zapewnić obiekt nie może być gromadzone:

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

Nie trzeba pinvoke FreeLibrary, user32.dll jest zawsze ładowany, aż program zakończy.

+0

Dzięki za pomoc. Teraz działa idealnie :) Problem rozwiązany. –

+0

jeśli User32 jest zawsze załadowany, dlaczego jawnie ładuję go w metodzie hook()? –

+0

Nie mam wehikułu czasu, aby wiedzieć, czy nadal będzie prawdziwy za 20 lat. –

Powiązane problemy