检测到“CallbackOnCollectedDelegate”问题。

21

我正在子类化一个应用程序。我的子类窗口过程在一个DLL中。我的DLL中的子类代码看起来有点像这样(精简了、删除了其他不相关的部分)。

class FooBar
{
  private delegate int WndProcDelegateType(IntPtr hWnd, int uMsg, 
                                           int wParam, int lParam);

  private const int GWL_WNDPROC = (-4);
  private static IntPtr oldWndProc = IntPtr.Zero;
  private static WndProcDelegateType newWndProc = new 
                                                  WndProcDelegateType(MyWndProc);

  internal static bool bHooked = false;

  [DllImport("user32.dll")]
  private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, 
                                             WndProcDelegateType dwNewLong);

  [DllImport("user32.dll")]
  private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, 
                                             IntPtr dwNewLong);


  [DllImport("user32")]
  private static extern int CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, 
                                           int Msg, int wParam, int lParam);

  private static int MyWndProc(IntPtr lhWnd, int Msg, int wParam, int lParam)
  {
    switch (Msg)
    {
      // the usual stuff


      // finally
      return CallWindowProc(oldWndProc, lhWnd, Msg, wParam, lParam);
    }


  internal static void Hook()
  {
    oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, MyWndProc);
    bHooked = oldWndProc != IntPtr.Zero;
  }

  internal static void Unhook()
  {
    if (bHooked) SetWindowLong(hWnd, GWL_WNDPROC, oldWndProc);
  }
}

尽管我将WndProc的强引用保存在类级别的静态实例变量中,但仍然会出现此错误。

检测到回调已被垃圾回收的委托

消息:在类型为“PowerPointAddIn1!FooBar + WndProcDelegateType :: Invoke”的已垃圾回收的委托上进行了回调。这可能导致应用程序崩溃,损坏和数据丢失。当向非托管代码传递委托时,必须通过托管应用程序保持其活动状态,直到可以确保它们永远不会被调用。

我做错了什么?


即使您已按照说明执行,如果仍然遇到此问题,请确保在此示例中,FooBar对象也没有被垃圾回收,因为引用将丢失。在调试期间,它可能运行良好,但在带有优化的发布构建中,我开始大惑不解。 - prostynick
注意 - 我刚刚回滚了问题文本上的第三次编辑,因为它使代码不再与已接受的答案匹配。它看起来像是被编辑以反映更正的代码 - 但这使得它更加混乱。 - StayOnTarget
3个回答

35
oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, MyWndProc);

这将强制C#即时创建一个委托对象。 它将代码转换为:

oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, new WndProcDelegateType(MyWndProc));

这是一个问题,该委托对象没有被任何地方引用。下一次垃圾回收将销毁它,使非托管代码失去依靠。你已经在代码中做了正确的事情,只是忘记使用它了。修复:

oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, newWndProc);

从NativeWindow派生自己的类并使用其AssignHandle()方法是更好的鼠标陷阱。在收到WM_DESTROY消息时调用ReleaseHandle()。


1
谢谢你的帮助。你的答案是正确的,但我仍然遇到了异常。非常抱歉我发布了错误的代码。在发布这个问题之前,我已经做了那个更改。我有两个地方都有这段代码,我发布了早期的代码,而我没有改变它。我仍然遇到了异常。 - Water Cooler v2
@Hans:抱歉,我没听懂你的意思。我有没有漏掉什么没解释清楚? - Water Cooler v2
回调函数可以在调用返回后被调用,托管调用者必须采取措施确保委托在回调函数完成之前不被收集。有关防止垃圾回收的详细信息,请参阅使用平台调用进行Interop封送。http://msdn.microsoft.com/en-us/library/eaw10et3.aspx - Elshan
Hans,我已经数不清你的回答对我有多大的帮助了。再次感谢你! - StayOnTarget
这也是我的问题。我将静态类的静态方法作为回调函数传递,所以我想“它怎么可能被垃圾回收了?!”但实际上被回收的是那个隐式委托。MDA错误应该提到这是错误的一个可能原因! - Matt Chambers
显示剩余5条评论

10

别说我疯了,但是存储一个引用应该可以解决这个问题:

 private static readonly WndProcDelegateType _reference = MyWndProc;  

2
谢谢。我以为我已经有了一个引用。无论如何,你的声明和我的唯一区别就是我使用了new运算符,而你没有。我尝试了你建议的方法,但仍然抛出相同的异常。 :-( - Water Cooler v2

2
回调函数可以在调用返回后被调用,但是托管调用方必须采取措施确保委托在回调函数完成之前不被收集。有关防止垃圾回收的详细信息,请参阅使用平台调用进行互操作封送。 http://msdn.microsoft.com/en-us/library/eaw10et3.aspx

1
谢谢。我正在忙着做某事。我有机会时会研究你提到的链接。非常感谢你的帮助。 - Water Cooler v2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接