On Monday, 7 May 2012 at 21:07:15 UTC, Steven Schveighoffer wrote:
I guess I don't really understand that. Who is responsible for cleaning up your class instance? The way I was understanding your description, I thought it was the C window runtime calling a callback you provide to it. Why do you need to have the GC clean it up?


Ah, I see why that's confusing.

Here's a (hopefully better) explanation:

Just as with any other object that represents an outside resource (e.g. a File/Stream/whatever), the lifetime of the unmanaged object should always follow the lifetime of the managed object.

In other words, this means that the creation of a Window object MUST be associated with the system call CreateWindow() (which in turns calls the window dispatcher function, WndProc, with the message WM_CREATE).

And, more importantly, this means that if the GC collects the Window object, then DestroyWindow() MUST be called on the kernel object, so that the window handle doesn't get leaked.

Just as with any other resource.


The trouble is that, as-is, this behavior is NOT implementable with a simple Window class whose constructor calls CreateWindow() and whose destructor calls DestroyWindow().

Why? Because if you were to do that in the constructor or destructor, the system would call back your WndProc() function, which is a *virtual* function meant to be overridden in the derived classes (so that they can handle events, such as the creation/destruction of the window, or the calculation of the window size, etc. properly).

That would mean your WndProc() in the derived instance would be called *before* the constructor of the derived instance is called, which is obviously not what you want.

Ditto with the destructor -- if you were to call DestroyWindow() in the ~Window(), then it would call back WndProc with the message WM_DESTROY in the derived class.



The only solution I see is to somehow call DestroyWindow() *BEFORE* you call the destructor, and call the destructor manually when the WM_DESTROY message is received. Ditto with the constructor: you need to somehow call CreateWindow() *BEFORE* you call the constructor, so that when you receive the WM_CREATE message, you can call the constructor manually.

Only by hijacking the construction and destruction call can you guarantee that the construction/destruction happens in an orderly fashion, i.e. that the kernel object lifetime tracks the user object lifetime properly. Otherwise you either miss getting some notifications at critical points (which results in incorrect code, which some people may or may not care about), or you risk getting a handle leak (also obviously bad).

Or you risk making the design pattern pretty horrific, kind of like how C# has a "NativeWindow" inside of the Control class, and they both have creation parameters, and they both register themselves ("park" themselves) globally on a static/shared field, and run into all sorts of ugly issues that you shouldn't need to run into. (If you look at the code and understand what they're doing, you'll see what I mean when I say it's ugly...)


Summary: Yes, I need the GC so I can manage the HWND lifetime properly, i.e. so it tracks the lifetime of the Window object (otherwise you can easily leak a handle). But in order to do this, I also need to be able to call the constructor/destructor manually, because the C code does this through a callback, which isn't possible when your object is being GC'd, due to virtual method re-entrancy issues with finalization.


Does that make sense? Is any part of it still unclear?

Reply via email to