Страницы

Поиск по вопросам

суббота, 14 декабря 2019 г.

Сборщик мусора вызывает CallbackOnCollectedDelegate

#c_sharp #сборщик_мусора


В проекте нужно перехватывать глобально нажатие определенной клавиши, на определенном
этапе приложение на третьем нажатии начало выдавать CallbackOnCollectedDelegate, принудительно
вызвал GC.Collect в конце события нажатия клавиши, ошибка стала вылетать сразу после
нажатия клавиши. Сделал пустой проект с таким же перехватом клавиш, ситуация осталась.

Program.cs стандартная

static void Main()
{

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}


Код Form1

public partial class Form1 : Form
{
    globalKeyboardHook gkh = new globalKeyboardHook();
    public Form1()
    {
        InitializeComponent();
        gkh.HookedKeys.Add(Keys.F12);
        gkh.KeyUp += gkh_KeyUp;
    }

    void gkh_KeyUp(object sender, KeyEventArgs e)
    {
        Console.WriteLine("test");
        GC.Collect();
    }
}


Исключение

Необработанное исключение: System.NullReferenceException: Ссылка на объект не указывает
на экземпляр объекта.
в System.StubHelpers.StubHelpers.CheckCollectedDelegateMDA(IntPtr pEntryThunk)



 Информацию не смог найти что за pEntryThunk и вообще System.StubHelpers не подключен
в проекте, прошу помощи



globalKeyboardHook - класс который используется для перехвата клавиш, исходный код:

https://www.codeproject.com/articles/19004/a-simple-c-global-low-level-keyboard-hook

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

namespace Utilities {
    /// 
    /// A class that manages a global low level keyboard hook
    /// 
    class globalKeyboardHook {
        #region Constant, Structure and Delegate Definitions
        /// 
        /// defines the callback type for the hook
        /// 
        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
        /// 
        /// The collections of keys to watch for
        /// 
        public List HookedKeys = new List();
        /// 
        /// Handle to the hook, need this to unhook and call the next hook
        /// 
        IntPtr hhook = IntPtr.Zero;
        #endregion

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

        #region Constructors and Destructors
        /// 
        /// Initializes a new instance of the  class
and installs the keyboard hook.
        /// 
        public globalKeyboardHook() {
            hook();
        }

        /// 
        /// Releases unmanaged resources and performs other cleanup operations before the
        ///  is reclaimed by garbage collection and
uninstalls the keyboard hook.
        /// 
        ~globalKeyboardHook() {
            unhook();
        }
        #endregion

        #region Public Methods
        /// 
        /// Installs the global hook
        /// 
        public void hook() {
            IntPtr hInstance = LoadLibrary("User32");
            hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
        }

        /// 
        /// Uninstalls the global hook
        /// 
        public void unhook() {
            UnhookWindowsHookEx(hhook);
        }

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

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

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

        /// 
        /// Loads the library.
        /// 
        /// Name of the library
        /// A handle to the library
        [DllImport("kernel32.dll")]
        static extern IntPtr LoadLibrary(string lpFileName);
        #endregion
    }
}

    


Ответы

Ответ 1



Ок, смотрите, в коде навески хука происходит следующее: public void hook() { IntPtr hInstance = LoadLibrary("User32"); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0); } Этот код эквивалентен вот такому: hInstance = LoadLibrary("User32"); keyboardHookProc temp = new keyboardHookProc(hookProc); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, temp, hInstance, 0); Т.е. создается экземпляр делегата, который передается в native code. При этом в managed code ссылка на делегат не сохраняется. А значит этот экземпляр делегата может быть собран сборщиком мусора. И в следующий раз при попытке нативного кода вызвать callback он не вызовется, а упадет с эксепшеном (как у вас в вопросе). Фикс - явно захватить делегат в поле класса globalKeyboardHook: Добавить поле: private keyboardHookProc _keyboardHookProc; И переписать метод hook как hInstance = LoadLibrary("User32"); _keyboardHookProc = new keyboardHookProc(hookProc); hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardHookProc, hInstance, 0); На самом деле проблема упомянута в комментариях на codeproject, и я просто скопировал оттуда фикс :)

Комментариев нет:

Отправить комментарий