Обнаружено событие CallbackOnCollectedDelegate

Рейтинг: 1Ответов: 2Опубликовано: 26.03.2015

Приложение - обыкновенная форма, которая скрывается при нажатии на кнопку и активном чекбоксе и появляется при нажатии клавиши Умножения (*).

Примерно через 50 таких нажатий появляется ошибка:

Был произведен обратный вызов делегата типа "WindowsFormsApplication3!Utilities.globalKeyboardHook+keyboardHookProc::Invoke", полученного сборщиком мусора.

В программе включил сборщик мусора, чтобы ошибка появлялась быстрее.

Основная программа:

using System.Windows.Forms;
using System.Diagnostics;
using System;
using System.Runtime.InteropServices;
using Utilities;

namespace KeyboardHook
{
public partial class Form1 : Form
{
    globalKeyboardHook gkh = new globalKeyboardHook();

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        if (checkBox1.Checked)
        {
            this.Hide();
        }
        else
            this.Show();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        gkh.HookedKeys.Add(Keys.Multiply);
        gkh.KeyDown += new KeyEventHandler(gkh_KeyDown);
    }

    void gkh_KeyUp(object sender, KeyEventArgs e)
    {
        e.Handled = true;
    }

    void gkh_KeyDown(object sender, KeyEventArgs e)
    {
        this.Show();
        GC.Collect(); /* С этой строкой ошибка вылезает сразу,без нее примерно через 50 нажатий */
        e.Handled = true;
    }

Модуль:

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

namespace Utilities {
    class globalKeyboardHook {
        #region Constant, Structure and Delegate Definitions
        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
    public List<Keys> HookedKeys = new List<Keys>();
    IntPtr hhook = IntPtr.Zero;
    #endregion

    #region Events
    public event KeyEventHandler KeyDown;
    public event KeyEventHandler KeyUp;
    #endregion

    #region Constructors and Destructors
    public globalKeyboardHook() {
        hook();
    }
    ~globalKeyboardHook() {
        unhook();
    }
    #endregion

    #region Public Methods
    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);
    }
    #endregion

    #region 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);
    #endregion
}
}

Ответы

▲ 3Принят

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 не уничтожен.