A handle leak in TWinControl?

For the TURBU engine, I’ve got a custom control that allows me to embed an OpenGL rendering context on a form.  I was working on some new features, and I downloaded gDEBugger, an OpenGL debugging tool, to help out.  One of the things it told me is that my rendering contexts were leaking.

They shouldn’t be.  I create the renderer in CreateWnd and I destroy it in DestroyWnd, which should get called as part of the component destruction process.  But as it turns out, DestroyWnd is not actually called anywhere!

By tracing through the VCL code, it looks like DestroyWnd is supposed to get called from a call stack that looks something like this:

TMyControl.DestroyWnd //virtual
TWinControl.DestroyHandle
TWinControl.RemoveControl
TControl.SetParent(nil)
TControl.Destroy

But earlier in the form destruction process, TWinControl.Remove gets called.  It does not call RemoveControl on the control being removed, and at the end of the routine, it says:

AControl.FParent := nil;

By directly setting the field to nil instead of calling SetParent(nil), it makes it so that when SetParent(nil) is called, the entire body of code gets short-circuited, so that RemoveControl is never called.  In fact, it’s not just my control.  I put a breakpoint on the first line of TWinControl.DestroyHandle, ran the program, and closed it, which generates and then frees almost 30 TWinControl descendants.  The breakpoint was never hit.

I’m seeing this in Delphi XE.  I haven’t tested it in any other version, but from what I can see, it appears that any VCL control created in XE will never call DestroyHandle during component destruction, leading to all window handles (and other resources, such as my rendering contexts) being leaked.

Can anyone confirm this, or say if it affects other versions?  I didn’t see this in QC, so I wrote up a bug report.  Please vote for it, especially if you work on a program with lots of complex forms and have users who would be, shall we say, unamused to suddenly hit an “out of resources” error for no good reason…

8 Comments

  1. Remy Lebeau says:

    I would be very surprised if this is true. It would be a MAJOR MAJOR regression that should never have gotten through QA. A lot of VCL components depend on DestroyWnd() being called so they can clean up resources, cache HWND-dependant data until CreateWnd() is called again, etc.

    • Mason Wheeler says:

      True enough, but you can test it yourself. Open some project, put a breakpoint on TWinControl.DestroyHandle (or on whatever.DestroyWnd, which gets called from DestroyHandle), load the form and then close it, and see that the breakpoint doesn’t get hit.

  2. Bummi says:

    It’s call on explicit call of e.g. FreeAndNil(Panel1);

    Seems to be a major bug….

  3. Alex says:

    I don’t think it’s a bug.

    DestroyWindowHandle is called for the form, which causes window handle to be gone – as well as any windows inside it: that includes any Delphi component handles too.

    I think that’s why VCL doesn’t explicitly release handles by itself – because child windows will be destroyed as part of parent window destruction, no need to release it; just delete Delphi object itself.

    • Mason Wheeler says:

      OK, different entry point. That makes sense. But I’ve been tracing through that code path, and it appears that it eventually leads to TWinControl.WMNCDestroy, which sets WindowHandle := 0; but without ever actually calling DestroyHandle. Maybe Windows will take care of the actual HWND, but if you have any custom cleanup code that’s supposed to be happening at that point, it will not run.

  4. Seppy Bloom says:

    Windows sends a WM_DESTROY message to control which is where you should do the cleanup (see http://msdn.microsoft.com/en-us/library/windows/desktop/ms632620(v=vs.85).aspx).

Leave a Reply to Mason Wheeler