Defining a custom *constructor* (not initializer!)
Mehrdad
wfunction at hotmail.com
Mon May 7 16:00:22 PDT 2012
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?
More information about the Digitalmars-d
mailing list