我正在使用鼠标和键盘窗口钩子(Hook)编写一个 C# 空闲监视器
public class KeyboardHook : WindowsHook
{
private static event KeyEventHandler _KeyDown = null;
public static void Hook(KeyEventHandler onKeyDown)
{
_KeyDown = onKeyDown;
SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback); // PROBLEM!!
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)KeyboardMessage.WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
_KeyDown(null, new KeyEventArgs((Keys)vkCode));
}
return CallNextHookEx(nCode, wParam, lParam);
}
}
在上面的代码中,HookCallback 被传递到 SetWindowsHookEx 中。代码编译和运行正常,但在一段时间后,HookCallback 被垃圾收集,visual studio 调试器将捕获它。
在下面的代码中,HookCallback 是使用 _HookProc 间接分配的,到目前为止它似乎运行正常。我明白为什么下面的代码有效,但我不确定为什么当直接分配(如上所述)时,HookCallback 没有被引用(“存储”)在某个不会导致它被 GC 编辑的地方?
public class KeyboardHook : WindowsHook
{
private static HOOKPROC _HookProc = HookCallback; // saving a reference locally
private static event KeyEventHandler _KeyDown = null;
public static void Hook(KeyEventHandler onKeyDown)
{
_KeyDown = onKeyDown;
SetWindowsHookEx(WH_KEYBOARD_LL, _HookProc);
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
// ...
}
}
最佳答案
请记住,一般来说,系统不知道您要 P/Invoke 到哪些 native 函数。在大多数情况下,只要您传递给 native 函数的函数指针仅(可能)在该函数调用正在进行时使用,.NET 系统就已经足以确保可以调用该函数。
但在您的情况下,您正在传递一个函数指针,它将在 SetWindowsHookEx
返回后的其他时间点被调用。在这种情况下,您有责任确保代理存活足够长的时间。
而且,从表面上看,您已经找到了实现该目标的方法。
备选方案是 a) 传递给任何 native 函数的任何委托(delegate)都从不被垃圾回收,从而造成可能的内存泄漏,或者 b) 系统必须为您的代码 以指示它何时知道不再需要委托(delegate)。但是这样的机制仍然需要代码的协作,而不仅仅是让您管理托管对象的生命周期。
关于c# - 为什么委托(delegate)函数引用在作为参数传递时会被垃圾收集?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23014380/