I installed a thread-specific windows hook to monitor messages sent to WndProc. It worked at first. However, after I pressed Tab about 19 times to move focus around a form, my hook callback is nolonger called. This happened regaless of whether I pressed Tab quickly or slowly. Can anybody explain what is really going on?
Below is the code I wrote. I tested it on Windows 7 64 bit.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace HookTest
{
static class Program
{
private const int WH_CALLWNDPROC = 4;
private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private class MainForm : Form
{
private Button button1;
private TextBox textBox1;
public MainForm()
{
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 38);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "Button 1";
this.button1.UseVisualStyleBackColor = true;
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 12);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 1;
//
// MainForm
//
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "MainForm";
this.Text = "Main Form";
this.ResumeLayout(false);
this.PerformLayout();
}
}
private static IntPtr hWndProcHook = IntPtr.Zero;
private static int messageCount = 0;
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
public static extern uint GetCurrentThreadId();
[DllImport("User32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SetWindowsHookEx(int idHook,
HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
InstallHook();
Application.Run(new MainForm());
UninstallHook();
}
private static void InstallHook()
{
if (Program.hWndProcHook == IntPtr.Zero)
{
Console.WriteLine("Hooking...");
Program.hWndProcHook = SetWindowsHookEx(
WH_CALLWNDPROC,
WndProcHookCallback,
GetModuleHandle(null),
GetCurrentThreadId());
if(Program.hWndProcHook != IntPtr.Zero)
Console.WriteLine("Hooked successfully.");
else
Console.WriteLine("Failed to hook.");
}
}
private static void UninstallHook()
{
if (Program.hWndProcHook != IntPtr.Zero)
{
Console.WriteLine("Unhooking...");
if (UnhookWindowsHookEx(Program.hWndProcHook))
Console.WriteLine("Unhooked successfully.");
else
Console.WriteLine("Failed to unhook.");
Program.hWndProcHook = IntPtr.Zero;
}
}
private static IntPtr WndProcHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
Console.WriteLine("WndProcHookCallback {0}", Program.messageCount++);
return CallNextHookEx(Program.hWndProcHook, nCode, wParam, lParam);
}
}
}
While testing your program I got the following error
CallbackOnCollectedDelegate was detected
Message: A callback was made on a garbage collected delegate of type 'Sandbox Form!Sandbox_Form.Program+HookProc::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.
I believe the problem is the delegate that is implicitly created to be passed in to SetWindowsHookEx
for the callback is getting garbage collected. By explicitly creating a variable for the delegate and keeping it in scope I think it will make your problem go away, when I modified InstallHook
to the following I could no longer re-create the error.
private static HookProc hookProcDelegate;
private static void InstallHook()
{
if (Program.hWndProcHook == IntPtr.Zero)
{
Console.WriteLine("Hooking...");
hookProcDelegate = new HookProc(WndProcHookCallback);
Program.hWndProcHook = SetWindowsHookEx(
WH_CALLWNDPROC,
hookProcDelegate,
GetModuleHandle(null),
GetCurrentThreadId());
if (Program.hWndProcHook != IntPtr.Zero)
Console.WriteLine("Hooked successfully.");
else
Console.WriteLine("Failed to hook.");
}
}