已在 C# 中对垃圾收集委托进行了调用?

Posted

技术标签:

【中文标题】已在 C# 中对垃圾收集委托进行了调用?【英文标题】:Call has been made on garbage collected delegate in C#? 【发布时间】:2011-05-31 21:21:40 【问题描述】:

我一直在使用我找到的这个关键挂钩脚本,但在我的程序中使用它几秒钟后,我仍然收到错误消息。错误说.. 已对垃圾收集委托“keylogger!Utilities.globalKeyboardHook+keyboardHookProc::Invoke”进行了调用。

我该如何解决这个问题?

namespace Utilities

/// <summary>
/// A class that manages a global low level keyboard hook
/// </summary>
class globalKeyboardHook


    #region Constant, Structure and Delegate Definitions
    /// <summary>
    /// defines the callback type for the hook
    /// </summary>
    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
    /// <summary>
    /// The collections of keys to watch for
    /// </summary>
    public List<Keys> HookedKeys = new List<Keys>();
    /// <summary>
    /// Handle to the hook, need this to unhook and call the next hook
    /// </summary>
    IntPtr hhook = IntPtr.Zero;
    #endregion

    #region Events
    /// <summary>
    /// Occurs when one of the hooked keys is pressed
    /// </summary>
    public event KeyEventHandler KeyDown;
    /// <summary>
    /// Occurs when one of the hooked keys is released
    /// </summary>
    public event KeyEventHandler KeyUp;
    #endregion

    #region Constructors and Destructors
    /// <summary>
    /// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
    /// </summary>
    public globalKeyboardHook()
    
        hook();
    

    /// <summary>
    /// Releases unmanaged resources and performs other cleanup operations before the
    /// <see cref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
    /// </summary>
    ~globalKeyboardHook()
    
        unhook();
    
    #endregion

    #region Public Methods
    /// <summary>
    /// Installs the global hook
    /// </summary>
    public void hook()
    

        IntPtr hInstance = LoadLibrary("User32");
        hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
    

    /// <summary>
    /// Uninstalls the global hook
    /// </summary>
    public void unhook()
    
        UnhookWindowsHookEx(hhook);
    

    /// <summary>
    /// The callback for the keyboard hook
    /// </summary>
    /// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
    /// <param name="wParam">The event type</param>
    /// <param name="lParam">The keyhook event information</param>
    /// <returns></returns>
    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
    /// <summary>
    /// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
    /// </summary>
    /// <param name="idHook">The id of the event you want to hook</param>
    /// <param name="callback">The callback.</param>
    /// <param name="hInstance">The handle you want to attach the event to, can be null</param>
    /// <param name="threadId">The thread you want to attach the event to, can be null</param>
    /// <returns>a handle to the desired hook</returns>
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);

    /// <summary>
    /// Unhooks the windows hook.
    /// </summary>
    /// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
    /// <returns>True if successful, false otherwise</returns>
    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);

    /// <summary>
    /// Calls the next hook.
    /// </summary>
    /// <param name="idHook">The hook id</param>
    /// <param name="nCode">The hook code</param>
    /// <param name="wParam">The wparam.</param>
    /// <param name="lParam">The lparam.</param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);

    /// <summary>
    /// Loads the library.
    /// </summary>
    /// <param name="lpFileName">Name of the library</param>
    /// <returns>A handle to the library</returns>
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);
    #endregion
    

globalKeyboardHook 类:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.IO;

namespace Utilities

/// <summary>
/// A class that manages a global low level keyboard hook
/// </summary>
class globalKeyboardHook : IDisposable


    private bool _disposed;

    #region Constant, Structure and Delegate Definitions
    /// <summary>
    /// defines the callback type for the hook
    /// </summary>
    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
    /// <summary>
    /// The collections of keys to watch for
    /// </summary>
    public List<Keys> HookedKeys = new List<Keys>();
    /// <summary>
    /// Handle to the hook, need this to unhook and call the next hook
    /// </summary>
    IntPtr hhook = IntPtr.Zero;
    #endregion

    #region Events
    /// <summary>
    /// Occurs when one of the hooked keys is pressed
    /// </summary>
    public event KeyEventHandler KeyDown;
    /// <summary>
    /// Occurs when one of the hooked keys is released
    /// </summary>
    public event KeyEventHandler KeyUp;
    #endregion

    #region Constructors and Destructors
    /// <summary>
    /// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
    /// </summary>
    public globalKeyboardHook()
    
        hook();
        _disposed = false;
    

    public void Dispose()
    
        Dispose(true);

        // Use SupressFinalize in case a subclass
        // of this type implements a finalizer.
        GC.SuppressFinalize(this);
    

    protected virtual void Dispose(bool disposing)
    
        // If you need thread safety, use a lock around these 
        // operations, as well as in your methods that use the resource.
        if (!_disposed)
        
            if (disposing)
            
                unhook();
            

            // Indicate that the instance has been disposed.
            _disposed = true;
        
    

    /// <summary>
    /// Releases unmanaged resources and performs other cleanup operations before the
    /// <see cref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
    /// </summary>
    ~globalKeyboardHook()
    
        Dispose();
    
    #endregion

    #region Public Methods
    /// <summary>
    /// Installs the global hook
    /// </summary>
    public void hook()
    
        IntPtr hInstance = LoadLibrary("User32");
        hhook = SetWindowsHookEx(WH_KEYBOARD_LL, new keyboardHookProc(hookProc), hInstance, 0);
    

    /// <summary>
    /// Uninstalls the global hook
    /// </summary>
    public void unhook()
    
        UnhookWindowsHookEx(hhook);
    

    /// <summary>
    /// The callback for the keyboard hook
    /// </summary>
    /// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
    /// <param name="wParam">The event type</param>
    /// <param name="lParam">The keyhook event information</param>
    /// <returns></returns>
    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
    /// <summary>
    /// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
    /// </summary>
    /// <param name="idHook">The id of the event you want to hook</param>
    /// <param name="callback">The callback.</param>
    /// <param name="hInstance">The handle you want to attach the event to, can be null</param>
    /// <param name="threadId">The thread you want to attach the event to, can be null</param>
    /// <returns>a handle to the desired hook</returns>
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);

    /// <summary>
    /// Unhooks the windows hook.
    /// </summary>
    /// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
    /// <returns>True if successful, false otherwise</returns>
    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);

    /// <summary>
    /// Calls the next hook.
    /// </summary>
    /// <param name="idHook">The hook id</param>
    /// <param name="nCode">The hook code</param>
    /// <param name="wParam">The wparam.</param>
    /// <param name="lParam">The lparam.</param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);

    /// <summary>
    /// Loads the library.
    /// </summary>
    /// <param name="lpFileName">Name of the library</param>
    /// <returns>A handle to the library</returns>
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);
    #endregion
    

我用 IDisposable 更新了代码。我可能对我应该做的事情感到非常失望,但它仍然无法正常工作

【问题讨论】:

@Tim 如果他在谈论如何编写内核驱动程序来捕获键盘输入,可能会想知道。但是,Windows 公开 API 来做到这一点的事实并不意味着任何使用它们的人都不好。有很多可能使用类似钩子的儿童保姆程序。 globalKeyboardHook 应该大写,而不是驼峰式。 @Andrew Finnell - 我有点开玩笑......只是他的程序集(我认为)被称为“键盘记录器”这一事实让我有点畏缩。 @Chris - 实现Dispose Pattern 以确保在类实例完成之前而不是在完成期间调用unhook 是的,从技术上讲,它是一个键盘记录器。但它也是一种保姆计划。它做了很多事情,但我遇到的唯一问题是这个键盘记录部分 【参考方案1】:

问题是:

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

只是语法糖:

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

因此keyboardHookProc 对象只是本地对象,并且将被丢弃,因为SetWindowsHookEx 并没有做任何事情来实际保留它在托管世界中。

要解决此问题,请在定义成员变量的顶部再添加一个,如下所示:

IntPtr hhook = IntPtr.Zero
private keyboardHookProc hookProcDelegate;

然后将您的构造函数更改为:

public globalKeyboardHook()

    hookProcDelegate = hookProc;
    hook();

然后将您的 hook() 方法更改为:

public void hook()

    IntPtr hInstance = LoadLibrary("User32");
    hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProcDelegate, hInstance, 0);

这样您就可以使用存储为成员变量的委托,只要您的globalKeyboardHook 对象还活着,它就会一直存在。

【讨论】:

我在回答中添加了更多细节。 这个解决方案对我有用,尽管我必须让我的类 var 静态。在那个改变之前,它仍在收集垃圾。 @Tim 我做了同样的实验,我的字段设置在一个方法中,我得到了同样的错误。当我将字段放入类构造函数中时,错误不再发生。为什么我必须在构造函数中进行字段初始化?没有意义?如果该字段在类级别声明但未在构造函数中实例化而是在随机方法中,则垃圾收集器无法获取它,因为它处于类级别。 GC 为什么要收集它?【参考方案2】:

在我看来,您正在实例化一个 globalKeyboardHook,然后让它收集垃圾。我猜你会这样做:

public void InstallHook()

    var hook = new globalKeyboardHook();

您需要保留对 globalKeyboardHook() 的引用,以防止它被垃圾收集。

globalKeyboardHook hook;

public void InstallHook()

    hook = new globalKeyboardHook();

【讨论】:

当持有引用的对象超出范围时,它仍然会遇到这种情况。该解决方案确实需要包含 IDisposable。【参考方案3】:

即使使用新代码我仍然收到上述错误,作为一种解决方案,我只是在类范围内保留了一个委托实例,现在该错误不再出现。

//do not forget to declare kbhproc class var   
this.kbhProc = new keyboardHookProc(hookProc);
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, this.kbhProc /*new keyboardHookProc(hookProc)*/, hInstance, 0);

以上代码是根据问题的代码编写的。

【讨论】:

嗯...这与 2.5 年前在这个问题上发布的答案基本相同,只是细节较少。【参考方案4】:

我想添加这个,以供将来参考,因为它可能有助于理解 Tim 的答案,如果你有复杂的代码,也许可以调试正在发生的事情:

callbackOnCollectedDelegate MDA

https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/callbackoncollecteddelegate-mda

【讨论】:

以上是关于已在 C# 中对垃圾收集委托进行了调用?的主要内容,如果未能解决你的问题,请参考以下文章

使用委托创建垃圾

Java虚拟机浅谈——垃圾收集器与内存分配策略

是否可以强制对象的垃圾收集?(java)

Java 中的垃圾收集器

垃圾收集器Serial ParallelCMSG1

C# 和 Java 中的垃圾收集