21

I am subclassing an application. My subclassed Window procedure is within a DLL. My subclassing code inside the DLL looks somewhat like this (stripped down, removed other non-related parts).

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

Now, even though I am holding a strong reference to the WndProc in a class-level static instance variable of the delegate, I get this error.

CallbackOnCollectedDelegate was detected

Message: A callback was made on a garbage collected delegate of type 'PowerPointAddIn1!FooBar+WndProcDelegateType::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

What am I doing wrong?

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Water Cooler v2
  • 32,724
  • 54
  • 166
  • 336
  • To anyone that still encounters this even if it seems you've done it as described - make sure that, in this example, `FooBar` object is not garbage collected too as the reference will be lost. During Debug it might work fine, but in Release build with optimizations it's when I've started to scratch my head. – prostynick Apr 09 '19 at 15:05
  • Note - I just rolled back edit#3 on the question text because it made the code no longer match the accepted answer. It looked like it had been edited to reflect the corrected code - but that made it much more confusing. – StayOnTarget Jan 14 '22 at 15:22
  • Potential dupe: [CallbackOnCollectedDelegate in globalKeyboardHook was detected](https://stackoverflow.com/questions/9957544/callbackoncollecteddelegate-in-globalkeyboardhook-was-detected) – StayOnTarget Jan 14 '22 at 15:39

3 Answers3

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

That forces C# to create a delegate object on-the-fly. It translates the code to this:

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

which is a problem, that delegate object isn't referenced anywhere. The next garbage collection is going to destroy it, pulling the rug out from under the unmanaged code. You already did the proper thing in your code, you just forgot to use it. Fix:

oldWndProc = SetWindowLong(hWnd, GWL_WNDPROC, newWndProc);

Deriving your own class from NativeWindow and uses its AssignHandle() method is the better mousetrap btw. Call ReleaseHandle() when you see the WM_DESTROY message.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    Thank you for your help. Your answer is correct but I still get the exception. I am terribly sorry that I posted the wrong code. I had already made that change before posting this question. I had the code in two places and posted the earlier code, which I had not changed. I still get the exception. – Water Cooler v2 Jan 31 '11 at 20:37
  • Erm, what am I supposed to look at? – Hans Passant Jan 31 '11 at 20:40
  • @Hans: Sorry, I didn't get you. Have I left something unexplained? – Water Cooler v2 Jan 31 '11 at 20:45
  • I recommend you use the NativeWindow class. – Hans Passant Jan 31 '11 at 20:55
  • Sure. Let me give that a try. Thanks. – Water Cooler v2 Jan 31 '11 at 20:58
  • @Hans: I removed all references to my WndProc and followed the NativeWindow route. The bugger still gives me the same ol' message about the WndProc delegate having been collected. Clearly, your solution would have worked. It just means that MS Office is still retaining a cache of the previous binaries somewhere. I deleted the bin several times but there's some other place it is holding on to those binaries from. – Water Cooler v2 Feb 01 '11 at 13:01
  • I've started a new question about this here. http://stackoverflow.com/questions/4863146/clear-microsoft-office-add-ins-cache – Water Cooler v2 Feb 01 '11 at 13:29
  • the callback function can be invoked after the call returns, the managed caller must take steps to ensure that the delegate remains uncollected until the callback function finishes. For detailed information about preventing garbage collection, see Interop Marshaling with Platform Invoke. http://msdn.microsoft.com/en-us/library/eaw10et3.aspx – Elshan May 27 '14 at 08:32
  • Hans I can't even count the # of times your answers have helped me enormously. Thanks (again)! – StayOnTarget Jan 14 '22 at 15:23
  • This was my problem as well. I was passing in a static method of a static class as the callback function, so I was like "how can it be getting garbage collected?!" But it was that implicit delegate that was getting collected. The MDA error should probably mention that as a likely cause for the error! – Matt Chambers Feb 17 '23 at 17:35
10

Call me crazy but storing a reference should resolve this:

 private static readonly WndProcDelegateType _reference = MyWndProc;  
ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
  • 2
    Thank you. I thought I already had a reference. Anyway, the only difference between your declaration and mine was that I used the new operator and you didn't. I tried it the way you suggested but it still throws the same exception. :-( – Water Cooler v2 Jan 31 '11 at 20:40
2

the callback function can be invoked after the call returns, the managed caller must take steps to ensure that the delegate remains uncollected until the callback function finishes. For detailed information about preventing garbage collection, see Interop Marshaling with Platform Invoke.

http://msdn.microsoft.com/en-us/library/eaw10et3.aspx

Elshan
  • 7,339
  • 4
  • 71
  • 106