Question

Here is keyboard hook code:

   class globalKeyboardHook
        {
            #region Constant, Structure and Delegate Definitions

            public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);
            public static keyboardHookProc callbackDelegate;

            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>();
            private static IntPtr hhook = IntPtr.Zero;

            #endregion

            #region Events

            public event KeyEventHandler KeyDown;
            public event KeyEventHandler KeyUp;

            #endregion

            #region Constructors and Destructors

            public globalKeyboardHook()
            {
                hook();
                GC.KeepAlive(callbackDelegate);
            }

            ~globalKeyboardHook()
            {
                unhook();
            }

            #endregion

            #region Public Methods

            public void hook()
            {
                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()
            {
                bool ok = UnhookWindowsHookEx(hhook);
                if (!ok) throw new Win32Exception();
                callbackDelegate = null;
            }

            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); // Exception on this line
            }
            #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
        }

When I initialize it in Child Form constructor first nothing happens, you can press all you want but nothing gets to event:

private globalKeyboardHook kh = new globalKeyboardHook();

public FrmMainForm(FrmVideo owner)
        {
            InitializeComponent();
            _videoForm = owner;

            try
            {
                _videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);

                if (_videoDevices.Count == 0)
                {
                    throw new ApplicationException();
                }

                foreach (FilterInfo device in _videoDevices)
                {
                    camerasCombo.Items.Add(device.Name);
                }

                camerasCombo.SelectedIndex = 1;
            }
            catch (ApplicationException)
            {
                camerasCombo.Items.Add("No local capture devices");
                _videoDevices = null;
            }

            kh.KeyUp += new KeyEventHandler(kh_KeyUp);
            kh.hook();
        }


    void kh_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.F12)
            _videoForm.TakeOver = !_videoForm.TakeOver;

        if (e.KeyCode == Keys.Escape)
            Application.Exit();
    }

Until I call piece of Aforge code for capturing from webcam, this starts streaming on Parent form:

  var videoSource = new VideoCaptureDevice(_videoDevices[camerasCombo.SelectedIndex].MonikerString)
        {
            DesiredFrameSize = new Size(Globals.FrameWidth, Globals.FrameHeight),
            DesiredFrameRate = 12
        };

        if (videoSource != null)
        {
            _videoForm.Device = videoSource;
            _videoForm.Start();
            button1.Enabled = false;
        }

After that on any keypress I get: CallbackOnCollectedDelegate was detected on return next hook, any ideas?

Oh, and I am unhooking on dispose of form:

   protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            kh.unhook();
            base.Dispose(disposing);
        }
Was it helpful?

Solution

This requires psychic debugging and infer something you didn't document. The oddity is that the MDA is triggered when you call CallNextHookEx(). Which is somewhat strange, the callback to your hook procedure actually worked. That delegate object wasn't collected. It is the next hook procedure call that failed.

There's a simple explanation for that: you called SetWindowsHookEx() more than once. Now using a static variable to store the delegate object will seriously bite, as static variables usually do, it is capable of storing only one delegate object. The second time you call hook(), it will overwrite the delegate of the first hook and it is thus no longer prevented from getting garbage collected. Which indeed triggers the MDA on the CallNextHookEx() since that will call the hook procedure for the first hook.

So your hook() method needs to be improved to this:

public void hook()
{
    if (callbackDelegate != null) 
        throw new InvalidOperationException("Cannot hook more than once");
    // etc..
}

It is actually not illegal to hook more than once, Windows doesn't mind. Just don't declare the variable static.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top