统计

最新评论

  • 1. re: 对.net事件的看法
  • @mdq 首先,在面向对象这种东西出现以前,已经有了事件的概念。否则的话键盘事件,鼠标事件怎么在dos时代就有了呢? 鼠标事件发生以后,不是OS在处理吗? 不是说要调用事件, 这种说法是错误的。 如果...
  • --信息加油站义工
  • 2. re: 对.net事件的看法
  • 我对楼主的观点不完全同意。我觉得事件体现的是封装性。因为事件都由对象内部的来进行激发,其他对象是不能去调用另一个对象的事件的 ,这样就保证了事件的发生条件或顺序不会发生改变,保证了设计的原意。 ...
  • --mdq
  • 3. re: 对.net事件的看法
  • bucuo
  • --ddd
  • 4. re: 独立宣言
  • 站在巨人的肩膀上...
  • --鱼的氧气
  • 5. re: 回忆一 --- 去年6月面试进入公司的日子
  • 从景仰到佩服...
  • --鱼的氧气

匿名方法引起的异常

  匿名方法引起的异常

问题:最近编写一个低级的键盘钩子,用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 信息加油站义工 阅读(1070) 评论(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 (仅博主可见) 
验证码 *  验证码看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论   新用户注册   返回页首      

导航: 网站首页 社区 新闻 博问 闪存 网摘 招聘 .NET频道 知识库 找找看 Google站内搜索



China-pub 计算机图书网上专卖店!6.5万品种 2-8折!
China-Pub 计算机绝版图书按需印刷服务

相关文章:

相关链接: