在globalKeyboardHook中检测到CallbackOnCollectedDelegate

本文关键字:CallbackOnCollectedDelegate 检测 globalKeyboardHook | 更新日期: 2023-09-27 18:29:40

我使用的是一个全局键盘挂钩类。此类允许检查是否在任何地方按下了键盘键。过了一段时间,我出现了一个错误:

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

这里是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;
        }
        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

有什么办法吗?这个程序运行得很好,但过了一段时间,程序冻结了蚂蚁,我出现了这个错误。

在globalKeyboardHook中检测到CallbackOnCollectedDelegate

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

这是你的问题。您依赖于C#语法sugar让它自动创建一个委托对象到hookProc。实际代码生成如下所示:

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

只有一个对委托对象$temp的引用。但它是局部变量,一旦hook()方法停止执行并返回,它就会消失。垃圾收集器在其他方面无法看到Windows也有对它的"引用",它无法探测非托管代码中的引用。因此,下次垃圾收集器运行时,委托对象将被销毁。当Windows进行挂钩回调时,这是一个笑话。内置的MDA在程序因AccessViolation而崩溃之前检测问题并生成有用的诊断。

您将需要创建一个对委派对象的附加引用,该引用可以保存足够长的时间。例如,您可以使用GCHandle。或者更简单,只需自己存储一个引用,这样垃圾收集器就可以始终看到该引用。在类中添加一个字段。使其静止是确保物体不会被收集的可靠方法:

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

无需pinvokeFreeLibrary,在程序终止之前,user32.dll始终处于加载状态。

没花太长时间就完成了!以下是一个非常好的工作实现,其中包含Hans-Passant的答案和GitHub项目之后的最新修复程序(定义和实现)。

//file Win32Api.cs
    using System;
    using System.Runtime.InteropServices;
    using YourProjectNamespace.Hooks;
    namespace YourProjectNamespace
    {
        public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);
        /// <summary>
        ///     Pcursor info
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct PCURSORINFO
        {
            public Int32 Size;
            public Int32 Flags;
            public IntPtr Cursor;
            public POINTAPI ScreenPos;
        }
        /// <summary>
        ///     Point
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct POINTAPI
        {
            public int x;
            public int y;
        }
        /// <summary>
        ///     keyboard hook struct
        /// </summary>
        public struct keyboardHookStruct
        {
            public int dwExtraInfo;
            public int flags;
            public int scanCode;
            public int time;
            public int vkCode;
        }
        /// <summary>
        ///     Wrapper for windows 32 calls.
        /// </summary>
        public class Win32Api
        {
            public const Int32 CURSOR_SHOWING = 0x00000001;
            public const int WH_KEYBOARD_LL = 13;
            public const int WM_KEYDOWN = 0x100;
            public const int WM_KEYUP = 0x101;
            public const int WM_SYSKEYDOWN = 0x104;
            public const int WM_SYSKEYUP = 0x105;
            [DllImport("user32.dll")]
            public static extern bool GetCursorInfo(out PCURSORINFO cinfo);
            [DllImport("user32.dll")]
            public static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
            [DllImport("winmm.dll")]
            private static extern int mciSendString(string MciComando, string MciRetorno, int MciRetornoLeng, int CallBack);
            [DllImport("user32")]
            private static extern int GetKeyboardState(byte[] pbKeyState);
            [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
            private static extern short GetKeyState(int vKey);
            [DllImport("user32")]
            public static extern int ToAscii(
                int uVirtKey,
                int uScanCode,
                byte[] lpbKeyState,
                byte[] lpwTransKey,
                int fuState);
            [DllImport("user32.dll", CharSet = CharSet.Auto,
                CallingConvention = CallingConvention.StdCall)]
            public static extern int CallNextHookEx(
                IntPtr idHook,
                int nCode,
                int wParam,
                ref keyboardHookStruct lParam);
            [DllImport("user32.dll", CharSet = CharSet.Auto,
                CallingConvention = CallingConvention.StdCall, SetLastError = true)]
            public static extern int UnhookWindowsHookEx(IntPtr idHook);
            [DllImport("user32.dll", CharSet = CharSet.Auto,
                CallingConvention = CallingConvention.StdCall, SetLastError = true)]
            public static extern IntPtr SetWindowsHookEx(
                int idHook,
                keyboardHookProc lpfn,
                IntPtr hMod,
                int dwThreadId);


            [DllImport("kernel32.dll")]
            public static extern IntPtr LoadLibrary(string dllToLoad);

            //Constants
        } // class Win32


 **File GlobalKeyboardHook.cs**

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using log4net;
    namespace ScreenRecorder.Hooks
    {
        public class GlobalKeyboardHook
        {
            private static readonly ILog log = LogManager.GetLogger(typeof (GlobalKeyboardHook).Name);
            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;
            private static keyboardHookProc callbackDelegate;

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


            /// <summary>
            public GlobalKeyboardHook()
            {
                Hook();
            }
            ~GlobalKeyboardHook() {
                Unhook();
            }
            public event KeyEventHandler KeyDown;
            public event KeyEventHandler KeyUp;

            public int HookProc(int nCode, int wParam, ref keyboardHookStruct lParam)
            {
                if (nCode >= 0)
                {
                    var key = (Keys) lParam.vkCode;
                    if (HookedKeys.Contains(key))
                    {
                        var kArgs = new KeyEventArgs(key);
                        if ((wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN) && (KeyDown != null))
                        {
                            KeyDown(this, kArgs);
                        }
                        else if ((wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP) && (KeyUp != null))
                        {
                            KeyUp(this, kArgs);
                        }
                        if (kArgs.Handled)
                            return 1;
                    }
                }
                return Win32Api.CallNextHookEx(keyboardHook, nCode, wParam, ref lParam);
            }

            public void Hook()
            {
                // Create an instance of HookProc.

                //if (callbackDelegate != null) throw new InvalidOperationException("Multiple hooks are not allowed!");
                IntPtr hInstance = Win32Api.LoadLibrary("User32");
                callbackDelegate = new keyboardHookProc(HookProc);
                //install hook
                keyboardHook = Win32Api.SetWindowsHookEx( Win32Api.WH_KEYBOARD_LL, callbackDelegate, hInstance, 0);
                //If SetWindowsHookEx fails.
                if (keyboardHook == IntPtr.Zero)
                {
                    //Returns the error code returned by the last unmanaged function called using platform invoke that has the DllImportAttribute.SetLastError flag set. 
                    var errorCode = Marshal.GetLastWin32Error();
                    log.Error("Unable to install keyboard hook.", new Win32Exception(errorCode));
                }
            }
            /// <summary>
            ///     Unsubscribe for keyboard hook
            /// </summary>
            public void Unhook()
            {
                if (callbackDelegate == null) return;

                if (keyboardHook != IntPtr.Zero)
                {
                    //uninstall hook
                    var retKeyboard = Win32Api.UnhookWindowsHookEx(keyboardHook);
                    //reset invalid handle
                    keyboardHook = IntPtr.Zero;
                    //if failed and exception must be thrown
                    if (retKeyboard == 0)
                    {
                        //Returns the error code returned by the last unmanaged function called using platform invoke that has the DllImportAttribute.SetLastError flag set. 
                        var errorCode = Marshal.GetLastWin32Error();
                        //Initializes and throws a new instance of the Win32Exception class with the specified error. 
                        log.Error("Error while uninstalling keyboard hook", new Win32Exception(errorCode));
                    }
                }
                callbackDelegate = null;
            }
        }

    }
相关文章:
  • 没有找到相关文章