#c_sharp #сборщик_мусора
В проекте нужно перехватывать глобально нажатие определенной клавиши, на определенном этапе приложение на третьем нажатии начало выдавать CallbackOnCollectedDelegate, принудительно вызвал GC.Collect в конце события нажатия клавиши, ошибка стала вылетать сразу после нажатия клавиши. Сделал пустой проект с таким же перехватом клавиш, ситуация осталась. Program.cs стандартная static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } Код Form1 public partial class Form1 : Form { globalKeyboardHook gkh = new globalKeyboardHook(); public Form1() { InitializeComponent(); gkh.HookedKeys.Add(Keys.F12); gkh.KeyUp += gkh_KeyUp; } void gkh_KeyUp(object sender, KeyEventArgs e) { Console.WriteLine("test"); GC.Collect(); } } Исключение Необработанное исключение: System.NullReferenceException: Ссылка на объект не указывает на экземпляр объекта. в System.StubHelpers.StubHelpers.CheckCollectedDelegateMDA(IntPtr pEntryThunk) Информацию не смог найти что за pEntryThunk и вообще System.StubHelpers не подключен в проекте, прошу помощи globalKeyboardHook - класс который используется для перехвата клавиш, исходный код: https://www.codeproject.com/articles/19004/a-simple-c-global-low-level-keyboard-hook using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Windows.Forms; namespace Utilities { ////// A class that manages a global low level keyboard hook /// class globalKeyboardHook { #region Constant, Structure and Delegate Definitions ////// defines the callback type for the hook /// 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; #endregion #region Instance Variables ////// The collections of keys to watch for /// public ListHookedKeys = new List (); /// /// Handle to the hook, need this to unhook and call the next hook /// IntPtr hhook = IntPtr.Zero; #endregion #region Events ////// Occurs when one of the hooked keys is pressed /// public event KeyEventHandler KeyDown; ////// Occurs when one of the hooked keys is released /// public event KeyEventHandler KeyUp; #endregion #region Constructors and Destructors ////// Initializes a new instance of the public globalKeyboardHook() { hook(); } ///class and installs the keyboard hook. /// /// Releases unmanaged resources and performs other cleanup operations before the /// ~globalKeyboardHook() { unhook(); } #endregion #region Public Methods ///is reclaimed by garbage collection and uninstalls the keyboard hook. /// /// Installs the global hook /// public void hook() { IntPtr hInstance = LoadLibrary("User32"); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0); } ////// Uninstalls the global hook /// public void unhook() { UnhookWindowsHookEx(hhook); } ////// The callback for the keyboard hook /// /// The hook code, if it isn't >= 0, the function shouldn't do anyting /// The event type /// The keyhook event information ///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); } #endregion #region DLL imports /// /// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null /// /// The id of the event you want to hook /// The callback. /// The handle you want to attach the event to, can be null /// The thread you want to attach the event to, can be null ///a handle to the desired hook [DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId); ////// Unhooks the windows hook. /// /// The hook handle that was returned from SetWindowsHookEx ///True if successful, false otherwise [DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hInstance); ////// Calls the next hook. /// /// The hook id /// The hook code /// The wparam. /// The lparam. ///[DllImport("user32.dll")] static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam); /// /// Loads the library. /// /// Name of the library ///A handle to the library [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); #endregion } }
Ответы
Ответ 1
Ок, смотрите, в коде навески хука происходит следующее: public void hook() { IntPtr hInstance = LoadLibrary("User32"); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0); } Этот код эквивалентен вот такому: hInstance = LoadLibrary("User32"); keyboardHookProc temp = new keyboardHookProc(hookProc); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, temp, hInstance, 0); Т.е. создается экземпляр делегата, который передается в native code. При этом в managed code ссылка на делегат не сохраняется. А значит этот экземпляр делегата может быть собран сборщиком мусора. И в следующий раз при попытке нативного кода вызвать callback он не вызовется, а упадет с эксепшеном (как у вас в вопросе). Фикс - явно захватить делегат в поле класса globalKeyboardHook: Добавить поле: private keyboardHookProc _keyboardHookProc; И переписать метод hook как hInstance = LoadLibrary("User32"); _keyboardHookProc = new keyboardHookProc(hookProc); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardHookProc, hInstance, 0); На самом деле проблема упомянута в комментариях на codeproject, и я просто скопировал оттуда фикс :)
Комментариев нет:
Отправить комментарий