callbackOnCollectedDelegate错误:托管代码到非托管代码

前几天写的键盘记录程序一直有个bug,没处理.

问题是这样的:  当我频繁不断的敲击键盘的时候, 过不一会儿,

程序就崩溃,自动关闭.

我的程序逻辑是这样的:

1: 首先,在系统钩子链表中注册一个全局键盘钩子.

2: 在这个键盘钩子的回调函数中转发键盘消息到我

的自处理线程.(转发时,设置标志位WM_User)

3: 在自处理线程中开一个循环. 不停的读取消息列表,

读到标志为WM_User的消息时,将其显示在ListBox中.

整个处理流程看起来很直观. 当时比较乱,一时间无法确定问题

到底出在哪里.

今天刚好有兴趣,翻出来代码详细看了下, 以Debug模式运行,

疯狂按了会儿键盘后, 程序抛出异常:

callbackOnCollectedDelegate

对“WindowsFormsApplication1!WindowsFormsApplication1.Form1+HookProc::Invoke”
类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。
向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。

原来是托管与非托管间的破事儿. 在MSDN中搜索 “callbackOnCollectedDelegate ”

找到这篇资料:callbackOnCollectedDelegate MDA

症状描述如下:

如果将一个委托作为函数指针从托管代码封送到非托管代码,并且在对该委托进行垃圾回收后对该函数指针发出了一个回调,则将激活 callbackOnCollectedDelegate 托管调试助手 (MDA)。

原因描述如下:

从其创建函数指针并将创建的函数指针公开给非托管代码的委托已被垃圾回收。当非托管组件尝试对该函数指针发出调用时,会产生访问冲突。

一旦将委托作为非托管函数指针封送出去,垃圾回收器就无法跟踪其生存期。这样,在该非托管函数指针的生存内,您的代码必须保持一个指向该委托的引用。

解决办法如下:

一旦将委托作为非托管函数指针封送出去,垃圾回收器就无法跟踪其生存期。这样,在该非托管函数指针的生存内,您的代码必须保持一个指向该委托的引用。但是在此之前,您首先必须确定回收了哪个委托。激活 MDA 之后,MDA 会提供该委托的类型名称。请使用此名称在您的代码中搜索将该委托外传给非托管代码的平台调用或 COM 签名。通过这些调用站点之一将有问题的委托传递出去。您还可以启用 gcUnmanagedToManaged MDA 以强制在每次向运行库发出回调之前都进行垃圾回收。这样可以确保在回调之前总是进行垃圾回,从而可以消除由垃圾回收引起的不确定性。一旦您得知回收了哪个委托,请更改您的代码,以便在封送的非托管函数指针的生存期内在托管端保持对该委托的引用。

这么大一块文字让人很晕. 简单来说:  这是一个由于CLR垃圾回收引起的问题. 由于将委托作为非托管

函数指针封送给非委托代码. 垃圾回收器就无法跟踪其生命周期.  解决办法就是, 将被过早垃圾回收的

委托置于整个对象的生存周期内.(这段话描述的绕,上代码解释)

HookProc p;
private void button1_Click(object sender, EventArgs e)
{

    //1, HookProc p = new HookProc(void ());

    //2, p = new HookProc(delegate(int code, IntPtr wParam, IntPtr lParam)
   {
       PostThreadMessage(hThreadId,
           Convert.ToUInt32(WM.USER),
           wParam,
           lParam);

       return code;
   });

第二种是良好的用法. 这样在整个对象的生存期内, p 就不会被垃圾回收了.

我对CLR的垃圾回收机制了解不多, 上面有很多地方说的不清楚,是因为我

自己也没法说清楚. 

我的程序换作上面代码的第二种用法就没再崩溃报错了.

总结:

1,  CLR垃圾回收无法跟踪封送给非托管代码的委托.

2,  非UI创建线程无法访问UI,必须由其创建线程访问. 一个解决办法是

调用 UI 对象的Invoke方法.

3,  若想对邦定到DataGridView中的数据进行增减,有两种方式.

     3.1  邦定DataTable对象到DataGridView

     3.2  若是想要邦定一些集合等自定义数据集, 使用 BindingSource作为

            一个中转.

     直接邦定自定义集合到DataGridView, 仅支持显示,无法增加. 至于删除

     或修改, 我没有测.

总结 2,3 是昨天遇到的另外两个问题, 一并总结在此.

欢迎大家指正,探讨.

posted on 2011-07-12 14:16  sunshaozong  阅读(1960)  评论(0编辑  收藏  举报