错误:CallbackOnCollectedDelegate - 如何找到确切的收集项目?

kum*_* DK 0 c# garbage-collection exception delegation

成功运行几分钟后,我的应用程序中出现此错误。

对类型为“myApp!myApp.globalKeyboardHook+keyboardHookProc::Invoke”的垃圾回收委托进行了回调。

我知道这是因为垃圾收集器杀死了一个对象或其他东西,而我的代码仍在引用相同的东西。

如果是这种情况,哪个对象或组件更有可能被 GC 收集。我怎样才能克服这个错误。(有参考??)

由于我无法弄清楚代码的哪一部分导致了这个问题,我在这里发布了完整的课程。(我想我的其他课程没有问题)

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

        IntPtr hInstance;
        const int WH_KEYBOARD_LL = 13;
        const int WM_KEYDOWN = 0x100;
        const int WM_SYSKEYDOWN = 0x104;

        public List<Keys> HookedKeys = new List<Keys>();
        IntPtr hhook = IntPtr.Zero;

        // Events
        public event KeyEventHandler KeyDown;

        public globalKeyboardHook()
        {
            hook();
        }

        ~globalKeyboardHook()
        {
            unhook();
        }

        public void hook()
        {
            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 (1 == 1)
                {
                    KeyEventArgs kea = new KeyEventArgs(key);
                    if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null))
                    {
                        KeyDown(this, kea);
                    }
                    if (kea.Handled)
                        return 1;
                }
            }
            return CallNextHookEx(hhook, code, wParam, ref lParam);
        }


        // DLL imports

        [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);

    }
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 5

    hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
Run Code Online (Sandbox Code Playgroud)

您肯定很难找到收集的项目,它在您的程序中不可见。C# 语言在这里有点太友好了。您依靠它的语法糖自动为hookProc. 这段代码被编译为:

    keyboardHookProc $unspeakable = new keyboardHookProc(hookProc);
    hhook = SetWindowsHookEx(WH_KEYBOARD_LL, $unspeakable, hInstance, 0);
Run Code Online (Sandbox Code Playgroud)

这是 pinvoke 中的一个问题,垃圾收集器不知道 $unspeakable 委托对象实际上正在被本机代码使用。它只能看到对对象的托管引用。因此,一旦发生 gen #0 垃圾收集,它就会收集对象。当 Windows 进行钩子回调时 Kaboom。

由您来确保无法收集此委托对象。使用 GCHandle.Alloc()。或者简单的方法,将它显式存储在一个变量中。这很好,因为您让终结器破坏了钩子:

    IntPtr hhook = IntPtr.Zero;
    keyboardHookProc callback;

    public void hook()
    {
        if (callback != null) throw new InvalidOperationException("Hook already installed");
        if (hInstance == IntPtr.Zero) hInstance = LoadLibrary("User32");
        callback = new keyboardHookProc(hookProc);
        hhook = SetWindowsHookEx(WH_KEYBOARD_LL, callback, hInstance, 0);
    }

    public void unhook()
    {
        UnhookWindowsHookEx(hhook);
        callback = null;
    }
Run Code Online (Sandbox Code Playgroud)