问题:最近编写一个低级的键盘钩子,用c#制作,于是用到了win32 api。但是运行大概不久后就会莫名其妙地发生异常,是非法访问内存导致的异常。
调试发现,异常的地方是不可捕获的。
| 以下是引用片段: static class Program { private static HookLog hookLog; /// /// The main entry point for the application. /// [STAThread] static void Main() { hookLog = new HookLog(Form1.appPath); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); Application.Run(new Form1()); } static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { hookLog.LogExceptionInfo(e.Exception); } } |
| 以下是引用片段: public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); |
钩子安装函数:
| 以下是引用片段: public void Setup() { if (handle != IntPtr.Zero) return; using(Process process = Process.GetCurrentProcess()) { using(ProcessModule module = process.MainModule) { //handle = Win32Api.SetWindowsHookEx(WindowsHookTypes.WH_KEYBOARD_LL, // new HookProc(ProcessKeyEvent), Win32Api.GetModuleHandle(module.ModuleName), // 0); handle = Win32Api.SetWindowsHookEx(WindowsHookTypes.WH_KEYBOARD_LL, kbdHookProc, Win32Api.GetModuleHandle(module.ModuleName), 0); if (handle != IntPtr.Zero) Win32Api.MessageBeep(MessageBeepTypes.IconExclamation); } } } |
但是通过调用win32 api --- SetWindowsHookEx,这个对象被传递给了非托管代码。这样就出现了问题。托管代码部分将把对象最为临时对象进行垃圾回收,因为发现没有被托管代码中的任何地方所引用。而非托管代码将仍然会调用这个回调函输,因为它无法知道托管代码已经释放了这个指针。这就是为什么运行不久后就发生一个无法捕获的越界访问异常的原因。
修改为下面一行代码,程序就正常了。
代码中kbdHookProc是在类中声明的成员变量:
| 以下是引用片段: /// /// 键盘钩子回调函数 /// private HookProc kbdHookProc; /// /// 构造函数 /// public LowLevelKeboardHook() { disposed = false; handle = IntPtr.Zero; kbdHookProc = new HookProc(ProcessKeyEvent); } /// /// 键盘钩子处理函数 /// private IntPtr ProcessKeyEvent(int nCode, IntPtr wParam, IntPtr lParam) { KBDLLHOOKSTRUCT? rfs = Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)) as KBDLLHOOKSTRUCT?; if (KeyboardEvent != null && rfs.HasValue) { int tag = wParam.ToInt32(); if(tag == (int)WindowsMessageTypes.WM_KEYDOWN || tag == (int)WindowsMessageTypes.WM_SYSKEYDOWN) KeyboardEvent(nCode, rfs.Value.vkCode, rfs.Value.flags); } return Win32Api.CallNextHookEx(handle, nCode, wParam, lParam); } |
所以使用匿名函数、匿名委托、匿名临时对象等等,当涉及到非托管代码时需要万分小心。
原文连接:http://www.cnblogs.com/worldreason/archive/2008/05/09/1190358.html