好天气看好电影

魔戒台词:刚铎和洛汉的好男儿,我的好兄弟;我在你们眼中...,看到了我心中也感受到的恐惧;有一天人类将失去勇气...,我们将众叛亲离,一败涂地;但不是今天;有一天邪将胜正,人类的世界也会完全毁灭;但不是今天;今天我们要誓死奋战。为了你们在世上所珍惜的一切...,一定要奋战到底
free hit counter

统计

最新评论

  • 1. re: 验证码的妙用
  • 楼主: SSL 和 VPN 是两个不同的概念。可以了解下... 前者主要用来加密传输的数据,而后者则用来限制外网的访问(当然也存在数据加密),从而提高安全性。 SSL: VPN: ...
  • --music000
  • 2. re: 回忆一 --- 去年6月面试进入公司的日子
  • 高人。
  • --呵呵哈哈111
  • 3. re: Mdi子窗口之闪烁问题的解决
  • 博主你好,我是一个刚学winform (VS 2005 C#)的学生.对你的文章很感觉兴趣,我碰到的问题就是你这文章上的问题, 被MDI窗口的这个闪烁问题困惑了很久,不知道在VS2005 里边怎么去改...
  • --liyunfeng
  • 4. re: 多年以前提高asp.net分页查询效率的一个实例
  • @michael_fei 我觉得一般在实际的应用中,对一个记录巨多的表,用id字段查询的情形少,对用户也只是个形式。如果排序字段不固定,那就是有了其它字段来过滤,只要这些字段上有索引什么的,就没必要采...
  • --信息加油站义工
  • 5. re: 多年以前提高asp.net分页查询效率的一个实例
  • 我觉得不知道有人是否了解这个方法的具体意义,也许是我搞错了。影响查询速度的根本原是select 出来的记录数量,用表变量,只要top 后的数字非常巨大,性能还是没法提高。我的方法是建立在已经解决问题的...
  • --信息加油站义工

匿名方法引起的异常

  匿名方法引起的异常

问题:最近编写一个低级的键盘钩子,用c#制作,于是用到了win32 api。但是运行大概不久后就会莫名其妙地发生异常,是非法访问内存导致的异常。

调试发现,异常的地方是不可捕获的。

static class Program
    {
        private static HookLog hookLog;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [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);
        }
    }

 

异常发生在Application.Run(new Form1())的代码行。

提示是HookProc回调函数被释放了。于是明白是怎么一回事了。

其中,HookProc是声明的一个委托回调类型:

public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

这个类型是与windows钩子的回调函输原型对应一致的。

钩子安装函数:

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);

         }

    }

}

代码中绿色注释掉的是引起异常的代码。因为直接使用了匿名委托。也就是生成的对象是个临时对象,new HookProc(ProcessKeyEvent),这个对象没有被保存。因此,对于托管代码的部分,这个委托回调函输对象将作为临时变量而存在。

但是通过调用win32 api --- SetWindowsHookEx,这个对象被传递给了非托管代码。这样就出现了问题。托管代码部分将把对象最为临时对象进行垃圾回收,因为发现没有被托管代码中的任何地方所引用。而非托管代码将仍然会调用这个回调函输,因为它无法知道托管代码已经释放了这个指针。这就是为什么运行不久后就发生一个无法捕获的越界访问异常的原因。

修改为被注释代码的下面一行代码,程序就正常了。

handle = Win32Api.SetWindowsHookEx(WindowsHookTypes.WH_KEYBOARD_LL, kbdHookProc, Win32Api.GetModuleHandle(module.ModuleName), 0);

代码中kbdHookProc是在类中声明的成员变量:

         /// <summary>
        /// 键盘钩子回调函数
        /// </summary>

        private HookProc kbdHookProc;
        /// <summary>
        /// 构造函数
        /// </summary>
        public LowLevelKeboardHook()
        {
            disposed = false;
            handle = IntPtr.Zero;
            kbdHookProc = new HookProc(ProcessKeyEvent);
        }

        /// <summary>
        /// 键盘钩子处理函数
        /// </summary>
        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);
        }


这样,看起来没多少变化。但是用类的成员变量记住了这个回调委托对象,因此就不会被垃圾回收所回收了。

所以使用匿名函数、匿名委托、匿名临时对象等等,当涉及到非托管代码时需要万分小心。




posted on 2008-05-09 17:53 信息加油站义工 阅读(994) 评论(7)  编辑 收藏 所属分类: 8. 信息技术1. dotnet

评论

#1楼  2008-05-09 19:09 alonesword [未注册用户]

这样的问题的确需要仔细研究代码,没有逻辑错误。   回复  引用    

#2楼  2008-05-09 19:51 曲滨*銘龘鶽      

其实原来用 VC 时,SetWindowsHookEx 给类内的实例函数都是很不适合

一般情况小都是给全局函数或静态函数;这样才不会担心变量被释放的问题;   回复  引用  查看    

#3楼  2008-05-10 10:24 求知无傲      

的键盘钩子可以用来做什么呢?万望请教   回复  引用  查看    

#4楼 [楼主] 2008-05-10 12:43 信息加油站义工      

@求知无傲

低级键盘钩子可以在赶在其它应用程序之前取得键盘输入。我当时这么做是为了帮助一个朋友,他想知道他女朋友经常聊天的内容是什么。哈哈。本来这种东西vc++做了更好,但是没那么多时间来帮别人阿,用c#做快的多,就用c#写了。   回复  引用  查看    

#5楼  2008-05-10 17:56 Jeffrey Zhao      

多谢提醒。   回复  引用  查看    

#6楼  2008-05-12 16:16 镜涛      

学习   回复  引用  查看    

#7楼  2008-06-11 16:41 JackieLi      

高科技的确是双刃剑,键盘钩子就是:)   回复  引用  查看    


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-06-09 19:41 编辑过


相关链接: