Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Dispatch of custom message handlers

489 views
Skip to first unread message

Enquiring Mind

unread,
Jun 18, 2008, 9:46:58 AM6/18/08
to
Hi,

From which method/procedure are custom message handlers (declared in say a
TForm class) dispatched? By custom message handler I mean a method declared
as:

const
WM_START_PROCESSING = WM_APP + 100;

procedure WMStartProcessing(var Message: TMessage); message
WM_START_PROCESSING;

I understand that the DispatchMessage API function calls a WndProc method
assigned to a system window. I have had a quick look at the chain of method
calls from TCustomForm.WndProc back to TControl.WndProc, but it's not clear
to me where the custom message handlers are dispatched. Can anyone clarify?

TIA,

EM


Yorai Aminov (TeamB)

unread,
Jun 18, 2008, 10:52:14 AM6/18/08
to
Enquiring Mind wrote:

> I understand that the DispatchMessage API function calls a WndProc
> method assigned to a system window. I have had a quick look at the
> chain of method calls from TCustomForm.WndProc back to
> TControl.WndProc, but it's not clear to me where the custom message
> handlers are dispatched. Can anyone clarify?

Messages are dispatched by TObject.Dispatch, called from
TControl.WndProc.


--
Yorai Aminov (TeamB)
(TeamB cannot answer questions received via email.)
Shorter Path - http://www.shorterpath.com
Yorai's Page - http://www.yoraispage.com

Remy Lebeau (TeamB)

unread,
Jun 18, 2008, 1:56:30 PM6/18/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:485911d2$1...@newsgroups.borland.com...

> From which method/procedure are custom message handlers (declared
> in say a TForm class) dispatched?

The TObject.Dispatch() method.

> I understand that the DispatchMessage API function calls a
> WndProc method assigned to a system window. I have had a
> quick look at the chain of method calls from TCustomForm.WndProc
> back to TControl.WndProc, but it's not clear to me where the
> custom message handlers are dispatched.

You did not follow the chain deep enough.

A window message first arrives to TWinControl.MainWndProc(), which passes
the message to the TControl.WindowProc property. By default, WindowProc
points to the TControl.WndProc() method. For TWinControl controls,
TWinControl overrides WndProc() and passes unhandled messages back to
TControl.WndProc(). TControl.WndProc() passes unhandled messages to
TObject.Dispatch(). Dispatch() checks if a suitable message handler is
present in the object's VMT for the message. If so, the message is passed
to it. Otherwise, the message is passed to TObject.DefaultHandler() instead
(which TControl and TWinControl both override).


Gambit


Enquiring Mind

unread,
Jun 19, 2008, 7:49:57 AM6/19/08
to

"Yorai Aminov (TeamB)" <yam...@gmail.com> wrote in message
news:4859211e$1...@newsgroups.borland.com...

>
> Messages are dispatched by TObject.Dispatch, called from
> TControl.WndProc.
>
>
Many thanks for that. I suspected that might be the case, as
TObject.Dispatch is called from TControl.WndProc, but since TObject.Dispatch
is written in assembler, I was unable to verify this.

Regards,

EM


Enquiring Mind

unread,
Jun 19, 2008, 8:05:11 AM6/19/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:48594c13$5...@newsgroups.borland.com...

>
>
>
> You did not follow the chain deep enough.

I followed it as far as I could go using Pascal!

>
> A window message first arrives to TWinControl.MainWndProc(), which passes
> the message to the TControl.WindowProc property. By default, WindowProc
> points to the TControl.WndProc() method. For TWinControl controls,
> TWinControl overrides WndProc() and passes unhandled messages back to
> TControl.WndProc(). TControl.WndProc() passes unhandled messages to
> TObject.Dispatch(). Dispatch() checks if a suitable message handler is
> present in the object's VMT for the message. If so, the message is passed
> to it. Otherwise, the message is passed to TObject.DefaultHandler()
> instead (which TControl and TWinControl both override).
>
>

Many thanks for this detailed explanation! The only missing link is that
TWinControl.MainWndProc calls a WindowProc event. This is assigned the
WndProc method in the constructor of TControl. Why is this additional
convolution necessary?

When using a custom message identifier obtained at run-time by calling
RegisterWindowMessage API function, one clearly cannot put the message
handling code in a VMT method having the 'message' directive. Is it better
to put the code in the WndProc method, or in an override of the
TObject.DefaultHandler?

In http://delphi.about.com/od/windowsshellapi/l/aa093003b.htm there's an
example where the custom WndProc calls the DefWindowProc API function to
handle all messages not specifically handled by the application. Would this
be necessary if the WndProc method were to call TObject.Dispatch?

Regards,

EM

Remy Lebeau (TeamB)

unread,
Jun 19, 2008, 3:12:12 PM6/19/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:485a4b7d$1...@newsgroups.borland.com...

> Many thanks for this detailed explanation! The only missing link is
> that TWinControl.MainWndProc calls a WindowProc event. This
> is assigned the WndProc method in the constructor of TControl.
> Why is this additional convolution necessary?

WindowProc is a public property. It allows for easy subclassing without
deriving new classes or calling SetWindowLong(GWL_WNDPROC) (which would not
be possible for non-TWinControl controls anyway). You can assign your own
handler to it in order to handle messages directly before the control does.

> When using a custom message identifier obtained at run-time by
> calling RegisterWindowMessage API function, one clearly cannot
> put the message handling code in a VMT method having the
> 'message' directive.

Correct. To handle those messages, you have to either assign a handler to
WindowProc, or else derive a new class that overrides WndProc().

> Is it better to put the code in the WndProc method

Yes.

> or in an override of the TObject.DefaultHandler?

No.

> In http://delphi.about.com/od/windowsshellapi/l/aa093003b.htm there's
> an example where the custom WndProc calls the DefWindowProc API
> function to handle all messages not specifically handled by the
> application.

That WndProc() is directly attached to a HWND that AllocateHWnd() returns.
There is no other VCL WndProc() methods in the chain. Messages send to the
HWND go directly to the custom WndProc(). Thus, it needs to pass unhandled
messages directly to DefWindowProc(). There is no other dispatching
involved.

> Would this be necessary if the WndProc method were to call
> TObject.Dispatch?

If it did that, unhandled messages (especially OS internal messages) would
stop working. The article code is using a custom WndProc() inside a
TThread, so there is no TWinControl present to call DefWindowProc()
automatically (actually, TWinControl uses CallWindowProc() instead, which
would ultimately call DefWindowProc() internally). TObject.Dispatch() calls
TObject.DefaultHandler() when no message handler is in the VMT.
TObject.DefaultHandler() itself does nothing, and TThread does not override
DefaultHandler() at all. TControl and TWinControl do override it, however.


Gambit


Enquiring Mind

unread,
Jun 20, 2008, 7:48:53 AM6/20/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:485a...@newsgroups.borland.com...

>
> WindowProc is a public property. It allows for easy subclassing without
> deriving new classes or calling SetWindowLong(GWL_WNDPROC) (which would
> not be possible for non-TWinControl controls anyway). You can assign your
> own handler to it in order to handle messages directly before the control
> does.
>
Thanks for the explanation.

> That WndProc() is directly attached to a HWND that AllocateHWnd() returns.
> There is no other VCL WndProc() methods in the chain. Messages send to
> the HWND go directly to the custom WndProc(). Thus, it needs to pass
> unhandled messages directly to DefWindowProc(). There is no other
> dispatching involved.
>
>> Would this be necessary if the WndProc method were to call
>> TObject.Dispatch?
>

The logic I had in mind for a class TThreadA (descending from TThread) that
is to be able to receive and handle messages is this:
1. Custom method TThreadA.WndProc calls inherited TObject.Dispatch;
2. TObject.Dispatch searches VMT of TThreadA class for message, and if found
executes it
3. If not found, TObject.Dispatch calls TThreadA.DefaultHandler overriding
TObject.DefaultHandler
4. The TThreadA.DefaultHandler calls DefWindowProc.

Thus:

TThreadA= class(TThread)
private
...
FWindowHandle: THandle; {Window handle for thread object}
protected
{Custom message handlers:}
procedure WMHandler0(var Message: TMessage); message WM_HANDLER_0;
procedure WMHandler1(var Message: TMessage); message WM_HANDLER_1;
{Message handler dispatching methods:}
procedure WndProc(var Message: TMessage);
procedure DefaultHandler(var Message); override;
{Top level thread procedure:}
procedure Execute; override;
...
public
constructor Create;
destructor Destroy; override;
property WindowHandle: THandle read FWindowHandle;
end;

procedure TThreadA.WndProc(var Message: TMessage);
begin
{Delegate message handling to object:}
Dispatch(Message);
end;

procedure TThreadA.DefaultHandler(var Message);
{Overrides virtual method TObject.DefaultHandler }
begin
{Message not in VMT, so pass it on to system for handling:}
with TMessage(Message) do
{.}Result:= DefWindowProc(FWindowHandle, {.}Msg, {.}wParam, {.}lParam);
end;

FWindowHandle is a private field of class TThreadA. It holds a handle
created using AllocateHWnd in the TTheadA's constructor.

Is there any reason why this approach shouldn't work?

Regards,

EM


Remy Lebeau (TeamB)

unread,
Jun 20, 2008, 1:27:31 PM6/20/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:485b...@newsgroups.borland.com...

> FWindowHandle is a private field of class TThreadA. It holds a handle
> created using AllocateHWnd in the TTheadA's constructor.

Calling AllocateHWnd() from the constructor will associate the HWND with the
wrong thread context. Thus, the TThread's message pump (which you must add
manually to Execute()) will not receive any messages.

The problem, though, is that AllocateHWnd() calls MakeObjectInstance()
internally, which is not thread-safe. To get around that, you can call
MakeObjectInstance() manually in the constructor (so it runs in the main
thread - provided the main thread is creating the worker thread), then call
AllocateHWnd() in Execute() with a nil TMethod parameter to bypass the
internal call to MakeObjectInstance(), and then finally call SetWindowLong()
manually to set up the HWND's window procedure. For example (untested):

TThreadA= class(TThread)
private
...
FWindowHandle: HWND;
FWndProcPtr: Pointer;
protected
...
procedure Execute; override;
procedure DoTerminate; override;
procedure DefaultHandler(var Message); override;
procedure WndProc(var Message: TMessage);
...
public
constructor Create; reintroduce;
destructor Destroy; override;
...
end;

constructor TThreadA.Create;
begin
FWndProcPtr := MakeObjectInstance(WndProc);
inherited Create(False);
end;

destructor TThreadA.Destroy;
begin
FreeObjectInstance(FWndProcPtr);
inherited Destroy;
end;

procedure TThreadA.Execute;
var
Message: TMsg;
begin
FWindowHandle := AllocateHWnd(nil);
SetWindowLong(FWindowHandle, GWL_WNDPROC, Longint(FWndProcPtr));
repeat
if MsgWaitForMultipleObjects(0, nil, False, 1000, QS_ALLINPUT) =
WAIT_OBJECT_0 then
begin
while PeekMessage(Message, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Message);
DispatchMessage(Message);
end;
end;
until Terminated;
end;

procedure TThreadA.DoTerminate;
begin
// don't call DeallocateHWnd() here as it will call
FreeObjectInstance()
// internally, but this is the wrong place for that! Just destroy
the HWND
// manually and call FreeObjectInstance() later
DestroyWindow(FWindowHandle);
FWindowHandle := 0;
inherited DoTerminate;
end;

procedure TThreadA.WndProc(var Message: TMessage);
begin

Dispatch(Message);
end;

procedure TThreadA.DefaultHandler(var Message);
begin
with TMessage(Message) do
Result := DefWindowProc(FWindowHandle, Msg, WParam, LParam);
end;

If you don't like the hack to make AllocateHWnd() work in a thread, then
just call the Win32 API CreateWindow/Ex() function manually instead.


Gambit


Enquiring Mind

unread,
Jun 21, 2008, 4:41:52 AM6/21/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:485be8c5$1...@newsgroups.borland.com...

>
> Calling AllocateHWnd() from the constructor will associate the HWND with
> the wrong thread context. Thus, the TThread's message pump (which you
> must add manually to Execute()) will not receive any messages.
>
What you say seems to make perfect sense, because constructor is called from
a different thread to that of Execute method. But then how does the example
in the web page (
http://delphi.about.com/od/windowsshellapi/l/aa093003b.htm ) work? It
allocates and deallocates the window handle in the constructor and
destructor of a TThread object.

> The problem, though, is that AllocateHWnd() calls MakeObjectInstance()
> internally, which is not thread-safe. To get around that, you can call
> MakeObjectInstance() manually in the constructor (so it runs in the main
> thread - provided the main thread is creating the worker thread), then
> call AllocateHWnd() in Execute() with a nil TMethod parameter to bypass
> the internal call to MakeObjectInstance(), and then finally call
> SetWindowLong() manually to set up the HWND's window procedure. For
> example (untested):
>

Many thanks for the sample code. I was interested to see an example of a
message loop in a thread Execute method (see recent thread on subject in
nativeapi.win32 NG). My first (untested) attempt at such a method is:

procedure TThreadA.Execute;
var
Msg: TMsg;
begin
AllocateWindowHandle;
repeat
WaitMessage;
{Is this call necessary? Doesn't GetMessage block anyway until a
message becomes available in the message queue?}
if GetMessage(Msg, FWindowHandle, 0, 0) then
{Could use 0 instead of FWindowHandle}
begin
TranslateMessage(Message);
DispatchMessage(Msg);
end
else
Terminate;
until Terminated;
end;

Using MsgWaitForMultipleObjects instead of WaitMessage has the advantage of
checking for a time-out condition. Arguably we need to periodically exit
from the wait to check whether or not a Terminate or Suspend signal has
arrived during the wait. So alternatively:

procedure TThreadA.Execute;
var
Message: TMsg;

WaitResult: cardinal;
begin
AllocateWindowHandle;
repeat
repeat
WaitResult:= MsgWaitForMultipleObjects(0, nil, False, 1000,
QS_ALLINPUT);
if WaitResult= WAIT_TIMEOUT then
{Inner loop will terminate if Terminated flag has been set
during
wait, otherwise waiting will resume}
DoBackgroundTask
else if WaitResult= WAIT_OBJECT_0 then
begin
{There's a message in the message queue, so retrieve it}
if GetMessage(Message, 0, 0, 0) then


begin
TranslateMessage(Message);
DispatchMessage(Message);
end;
end;

until Terminated or (WaitResult<>WAIT_TIMEOUT);
until Terminated;
end;


Regards,

EM


Remy Lebeau (TeamB)

unread,
Jun 21, 2008, 7:01:25 PM6/21/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:485cbf16$1...@newsgroups.borland.com...

> What you say seems to make perfect sense, because constructor is
> called from a different thread to that of Execute method.

Exactly.

> But then how does the example in the web page (
> http://delphi.about.com/od/windowsshellapi/l/aa093003b.htm ) work?

It is calling AllocateHWnd() in the constructor, so all messages for the
HWND will go to through the message queue of the thread that calls the
constructor (in this case, the main thread). Which defeats the whole
purpose of putting an HWND in a thread to begin with.

> It allocates and deallocates the window handle in the
> constructor and destructor of a TThread object.

Which is the wrong thing to do. The article's code has several bugs in it.
For instance, the whole "DeallocateHWnd() bug" section is bogus. The real
bug is not in DeallocateHWnd() at all, but in the thread code calling
DeallocateHWnd() from a different thread context than AllocateHWnd(). The
underlying call to DestroyWindow() would fail, which is why the HWND is able
to still receive messages after the WndProc() pointer had been freed.

> Many thanks for the sample code. I was interested to see an example
> of a message loop in a thread Execute method (see recent thread on
> subject in nativeapi.win32 NG). My first (untested) attempt at such
> a method is:

Was there a problem with the sample code I gave you for that purpose?

> WaitMessage;
> {Is this call necessary? Doesn't GetMessage block anyway until a
> message becomes available in the message queue?}

Yes, it does.

> if GetMessage(Msg, FWindowHandle, 0, 0) then

Despite what the documentation says, GetMessage() does not return a Boolean
True/False value. It returns an Integer -1/0/1 value instead. You need to
adjust your call to this:

if GetMessage(Msg, FWindowHandle, 0, 0) > 0 then

> Using MsgWaitForMultipleObjects instead of WaitMessage has the
> advantage of checking for a time-out condition.

Correct. That is why I picked it - so the thread can remain responsive to
checking its Terminated property periodically. Otherwise, to break the
blocking GetMessage() loop, you would have to post a WM_QUIT message to the
queue instead. Or, when using MsgWaitForMultipleObjects(), set up a
separate Event object that is signalled on termination, and then have
MsgWaitForMultipleObjects() wait on the Event and queue at the same time.

> else if WaitResult= WAIT_OBJECT_0 then
> begin
> {There's a message in the message queue, so retrieve it}
> if GetMessage(Message, 0, 0, 0) then

If you study my earlierr sample again, I use a PeekMessage() loop instead.
That way, if there are multiple messages in the queue by the time the code
reaches this point, the can all be processed at one time, so that
MsgWaitForMultipleObjects() is not called again until the queue is empty.
The way you are doing it, when any messages arrive, you only process one
message and then return to MsgWaitForMultipleObjects(), which will just exit
immediate if there were more messages available.


Gambit


Enquiring Mind

unread,
Jun 23, 2008, 10:10:33 AM6/23/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:485d880e$1...@newsgroups.borland.com...

>
>
> Was there a problem with the sample code I gave you for that purpose?

None whatsoever! But I wanted to better understand when it's most
appropriate to use GetMessage, PeekMessage, and WaitMessage, given that the
SDK uses GetMessage in its example of a message loop.

>
>> WaitMessage;
>> {Is this call necessary? Doesn't GetMessage block anyway until a
>> message becomes available in the message queue?}
>
> Yes, it does.

If GetMessage waits for a message to be available in the queue why does the
API also provide the WaitMessage function? At first sight it looks as though
GetMessage is equivalent to:

begin
WaitMessage;
PeekMessage;
end;

My initial thought is that WaitMessage might put the thread into an
efficient waiting state (i.e one that does doesn't consume processor cycles
in polling), whilst GetMessage might not.

>
> Despite what the documentation says, GetMessage() does not return a
> Boolean True/False value. It returns an Integer -1/0/1 value instead.
> You need to adjust your call to this:
>
> if GetMessage(Msg, FWindowHandle, 0, 0) > 0 then
>

Thanks very much for this warning! The SDK documentation itself breaks the
advice it gives in the article on GetMessage, because the article on Message
Loop gives the following example:

while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}


>
>> else if WaitResult= WAIT_OBJECT_0 then
>> begin
>> {There's a message in the message queue, so retrieve it}
>> if GetMessage(Message, 0, 0, 0) then
>
> If you study my earlierr sample again, I use a PeekMessage() loop instead.
> That way, if there are multiple messages in the queue by the time the code
> reaches this point, the can all be processed at one time, so that
> MsgWaitForMultipleObjects() is not called again until the queue is empty.
> The way you are doing it, when any messages arrive, you only process one
> message and then return to MsgWaitForMultipleObjects(), which will just
> exit immediate if there were more messages available.

I can see that omitting the inner loop might be inefficient, but will all
messages in the queue nevertheless be processed?


Regards,

EM


Enquiring Mind

unread,
Jun 23, 2008, 10:12:47 AM6/23/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:485be8c5$1...@newsgroups.borland.com...

>
>
> Calling AllocateHWnd() from the constructor will associate the HWND with
> the wrong thread context. Thus, the TThread's message pump (which you
> must add manually to Execute()) will not receive any messages.
>
> The problem, though, is that AllocateHWnd() calls MakeObjectInstance()
> internally, which is not thread-safe. To get around that, you can call
> MakeObjectInstance() manually in the constructor (so it runs in the main
> thread - provided the main thread is creating the worker thread), then
> call AllocateHWnd() in Execute() with a nil TMethod parameter to bypass
> the internal call to MakeObjectInstance(), and then finally call
> SetWindowLong() manually to set up the HWND's window procedure. For
> example (untested):
>
I didn't comment in detail on this the first time round because I didn't
really understand it on a first reading!

The approach is great but seems to demand more knowledge of the inner
workings of the AllocateHWnd procedure than a developer can hope to possess!
How can he/she know which calls are thread-safe? I was wondering,
therefore, whether any simpler approaches might be available? For example,
would it be possible to simply call AllocateHWnd protected within a
Synchronize call within the thread's Execute method ? Maybe something along
the following lines:

TThreadB= class(TThread)
private
FWindowHandle: HWnd;
procedure CreateThreadWindow;
procedure DestroyThreadWindow;
protected
procedure WndProc(var Message: TMessage);
..
public
...
end;

procedure TThreadB.CreateThreadWindow;
begin
FWindowHandle:= AllocateHWindow(WndProc);
end;

procedure TThreadB.DestroyThreadWindow;
begin
DeallocateHWindow(WndProc);
end;

procedure TThreadB.Execute;
begin
Synchronize(CreateThreadWindow);
try
repeat
DoThreadWorkCycle;
until Terminated;
finally
Synchronize(DestroyThreadWindow);
end
end;

>
> procedure TThreadA.Execute;
> var
> Message: TMsg;
> begin
> FWindowHandle := AllocateHWnd(nil);
> SetWindowLong(FWindowHandle, GWL_WNDPROC, Longint(FWndProcPtr));
> repeat
> if MsgWaitForMultipleObjects(0, nil, False, 1000, QS_ALLINPUT)
> = WAIT_OBJECT_0 then
> begin
> while PeekMessage(Message, 0, 0, 0, PM_REMOVE) do
> begin
> TranslateMessage(Message);
> DispatchMessage(Message);
> end;
> end;
> until Terminated;
> end;

Is the reason why PeekMessage is in an inner loop that the
MsgWaitForMultipleObjects function returns whenever there are one *or more*
messages in the thread's message queue, and that messages can continue being
appended to the queue while the thread is blocked?


>
> procedure TThreadA.DoTerminate;
> begin
> // don't call DeallocateHWnd() here as it will call
> FreeObjectInstance()
> // internally, but this is the wrong place for that! Just destroy
> the HWND
> // manually and call FreeObjectInstance() later
> DestroyWindow(FWindowHandle);
> FWindowHandle := 0;
> inherited DoTerminate;
> end;

I'm afraid that I don't understand what determines where DeallocateHWnd may
be called!

Regards,

EM


Remy Lebeau (TeamB)

unread,
Jun 23, 2008, 2:02:31 PM6/23/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:485faf2d$1...@newsgroups.borland.com...

> If GetMessage waits for a message to be available in the
> queue why does the API also provide the WaitMessage
> function?

Because there may be times when you want to wait for a message to arrive,
but not actually retrieve the new message right away. The VCL does exactly
that internally, for instance. Once the message queue becomes empty, the
TApplication.OnIdle event is fired and then WaitMessage() is called to put
the main thread to sleep until a new message arrives. Message handling is
performed in a separate section of code. So it makes sense to call
WaitMessage() and Peek/GetMessage() at different times.

> My initial thought is that WaitMessage might put the thread
> into an efficient waiting state (i.e one that does doesn't consume
> processor cycles in polling), whilst GetMessage might not.

GetMessage() also puts the thread into an efficient waiting state.

> I can see that omitting the inner loop might be inefficient, but
> will all messages in the queue nevertheless be processed?

Yes.


Gambit


Remy Lebeau (TeamB)

unread,
Jun 23, 2008, 2:10:12 PM6/23/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:485faf5b$1...@newsgroups.borland.com...

> The approach is great but seems to demand more knowledge of
> the inner workings of the AllocateHWnd procedure than a
> developer can hope to possess!

That is what installing the VCL source code is for ;-)

> How can he/she know which calls are thread-safe?

Experience and/or studying the source code.

> I was wondering, therefore, whether any simpler approaches
> might be available?

I already mentioned one earlier - call CreateWindow() directly instead of
AllocateHWnd().

> For example, would it be possible to simply call AllocateHWnd protected
> within a Synchronize call within the thread's Execute method ?

Although that would be thread-safe, it would cause the same problem as
calling AllocationHWnd() in the thread constructor - the resulting HWND
would be associated with the message queue of the main thread, not the
worker thread. Any message processing inside the thread would fail to see
any messages that are sent to the HWND.

> Is the reason why PeekMessage is in an inner loop that the
> MsgWaitForMultipleObjects
> function returns whenever there are one *or more* messages in the thread's
> message queue,
> and that messages can continue being appended to the queue while the
> thread is blocked?

I use the PeekMessage() loop so that MsgWaitForMultipleObject() does not
need to be called again until the queue has been emptied first. You already
know that messages exist in the queue. They can be processed in a single
batch loop, and then MsgWaitForMultipleObjects() can be used to start the
next batch later.

> I'm afraid that I don't understand what determines where DeallocateHWnd
> may be called!

DeallocateHWnd() calls DestroyWindow() internally. DestroyWindow() cannot
destroy an HWND that was created by another thread. Thus, DeallocateHWnd()
must be called in the same thread context that calls AllocateHWnd().


Gambit


Enquiring Mind

unread,
Jun 24, 2008, 11:49:54 AM6/24/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:485fe748$1...@newsgroups.borland.com...

>
>
>> I was wondering, therefore, whether any simpler approaches
>> might be available?
>
> I already mentioned one earlier - call CreateWindow() directly instead of
> AllocateHWnd().
>
Calling CreateWindow directly raises several questions:
a) What exactly does the pointer returned by the function
MakeObjectInstance refer to? Is the pointer returned the same for all
instances of the TThreadA class?
b) Is the class registration code that appears in the AllocateHWnd
procedure necessary?
c) If the answer to b) is yes, can the registered class be the same for all
instances of the TThreadA class, even though each will be associated with a
different thread window? IOW can the classname and the WndProcPtr be the
same for each TThreadA instance?
d) If the answer to c) is yes, why does a class that is already registered
need to be unregistered and then re-registered?

I suspect that at the end of the day it's probably easier to call
AllocateHWnd with a nil method reference than worry about all the above
details! However one wonders why MakeObjectInstance and FreeObjectInstance
have not been made thread-safe.

> I use the PeekMessage() loop so that MsgWaitForMultipleObject() does not
> need to be called again until the queue has been emptied first. You
> already know that messages exist in the queue. They can be processed in a
> single batch loop, and then MsgWaitForMultipleObjects() can be used to
> start the next batch later.

The disadvantage of this approach, as I see it, is that if several message
handlers block the thread, the caller has to wait for longer before the
thread can be suspended or terminated gracefully. With a
MsgWaitForMultipleObjects call before each message handler call the events
that allow the thread to be suspended or terminated are signalled and can be
acted upon as soon as each message handler is completed.
>

> DeallocateHWnd() calls DestroyWindow() internally. DestroyWindow() cannot
> destroy an HWND that was created by another thread. Thus,
> DeallocateHWnd() must be called in the same thread context that calls
> AllocateHWnd().
>

Thanks - that's much clearer.

Remy Lebeau (TeamB)

unread,
Jun 24, 2008, 1:20:31 PM6/24/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:4861179d$1...@newsgroups.borland.com...

> What exactly does the pointer returned by the
> function MakeObjectInstance refer to?

It is a pointer to a proxy function that will convert a WNDPROC call from
the OS into a TWndMethod call into the class method.

> Is the pointer returned the same for all instances of
> the TThreadA class?

No. It is bound to a specific instance. Internally, the proxy holds the
'Self' pointer that will be passed to the called WndProc() method.

> Is the class registration code that appears in the AllocateHWnd procedure
> necessary?

Yes.

> can the registered class be the same for all instances of the
> TThreadA class, even though each will be associated with a different
> thread window?

They already are the same window class - "TPUtilWindow". AllocateHWnd()
creates a new instance of the class each time it is called.

> can the ... WndProcPtr be the same for each TThreadA instance?

No. Each call to MakeObjectInstance() bounds the specified WndProc() method
to the specified object instance. Two objects cannot share a single
WndProcPtr.

> why does a class that is already registered need to be unregistered
> and then re-registered?

If you look at the code more closely, you will see that it does not do that
unless the TWndClass.lpfnWndProc member is not pointing at DefWindowProc()
anymore.

> However one wonders why MakeObjectInstance and
> FreeObjectInstance have not been made thread-safe.

Much of the VCL/RTL in general is not thread-safe. Many things, especially
UI elements, were not designed to be used outside the main thread.


Gambit


Enquiring Mind

unread,
Jun 25, 2008, 10:46:20 AM6/25/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:48612d2e$2...@newsgroups.borland.com...
>
Thanks for helpful explanations!

>> why does a class that is already registered need to be unregistered
>> and then re-registered?
>
> If you look at the code more closely, you will see that it does not do
> that unless the TWndClass.lpfnWndProc member is not pointing at
> DefWindowProc() anymore.
>

How does the TWndClass.lpfnWndProc pointer relate to the pointer assigned to
the window using SetWindowLong?

Below is a draft of a thread message handler class that uses the alternative
approach to creating a window object associated with the thread. Any
comments?

TThreadMessagesBase= class(TThreadBase)
private


FWindowHandle: THandle; {Window handle for thread object}

FWndProcPtr: Pointer; {Pointer to window procedure}
FOnBackgroundTask: TNotifyEvent;
function GetWindowClassname: PChar;
procedure CreateWindowObject;
procedure DestroyWindowObject;
procedure CreateWindowObjectThreadUnsafeCode;
procedure DestroyWindowObjectThreadUnsafeCode;
procedure WndProc(var Message: TMessage);
procedure CreateMessageQueue(var Done: Boolean);
protected
procedure DoBackgroundTask; virtual;
procedure HandleMessage;
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean; Owner: TObject; Caption:
string);
destructor Destroy; override;
procedure DefaultHandler(var Message); override;


property WindowHandle: THandle read FWindowHandle;

property OnBackgroundTask: TNotifyEvent read FOnBackgroundTask write
FOnBackgroundTask;
end;

procedure TThreadMessagesBase.CreateWindowObject;
var
HInstance: THandle;
begin
HInstance:= SysInit.HInstance;
Synchronize(CreateWindowObjectThreadUnsafeCode);
FWindowHandle := CreateWindowEx(WS_EX_TOOLWINDOW, GetWindowClassname,
'', WS_POPUP {!0}, 0, 0, 0, 0, 0, 0, HInstance, nil);
if FWindowHandle= 0 then
raise Exception.Create('Error in CreateWindow');
{Assign previously allocated WndProc pointer to window:}
SetWindowLong(FWindowHandle, GWL_WNDPROC, Longint(FWndProcPtr));
end;

procedure TThreadMessagesBase.CreateWindowObjectThreadUnsafeCode;

procedure RegisterWindowClass;
var
HInstance: THandle;
WindowClassname: PChar;
ClassRegistered: Boolean;
NewWndClassRecord, OldWndClassRecord: tagWNDCLASSEXA; {Window class
record}
Result: word;
begin
HInstance:= SysInit.HInstance;
WindowClassname:= GetWindowClassname;
ClearWindowClassRecord(NewWndClassRecord);
with NewWndClassRecord do
begin
lpfnWndProc:= @DefWindowProc;
hInstance:= SysInit.HInstance;
lpszClassName:= WindowClassname;
end;
ClassRegistered := GetClassInfoEx(HInstance, WindowClassname,
OldWndClassRecord);
if ClassRegistered then
begin
Windows.UnregisterClass(WindowClassname, HInstance);
raise Exception.Create('Error in RegisterWindowClass');
end
else
begin
{Register window class:}
Result:= Windows.RegisterClassEx(NewWndClassRecord);
if Result= 0 then
raise Exception.Create('Error in RegisterClassEx');
end;
end;

begin
if TThreadMessagesBaseInstanceCount= 0 then
RegisterWindowClass;
Inc(TThreadMessagesBaseInstanceCount);
{Get pointer to WndProc method:}
FWndProcPtr := MakeObjectInstance(WndProc);
end;

procedure TThreadMessagesBase.DestroyWindowObject;
begin
DestroyWindow(FWindowHandle);
FWindowHandle := 0;
Synchronize(DestroyWindowObjectThreadUnsafeCode);
end;

procedure TThreadMessagesBase.DestroyWindowObjectThreadUnsafeCode;

procedure UnregisterWindowClass;
var
HInstance: THandle;
WindowClassname: PChar;
ClassRegistered: Boolean;
WindowClassRecord: tagWNDCLASSEXA;
begin
HInstance:= SysInit.HInstance;
WindowClassName:= GetWindowClassname;
ClassRegistered := GetClassInfoEx(HInstance, WindowClassname,
WindowClassRecord);
if ClassRegistered then
Windows.UnregisterClass(WindowClassname, HInstance)
else
Raise Exception.Create('Error in UnregisterWindowClass');
end;

begin
{Release memory created for WndProc:}
FreeObjectInstance(FWndProcPtr);
Dec(TThreadMessagesBaseInstanceCount);
if TThreadMessagesBaseInstanceCount= 0 then
UnregisterWindowClass;
end;

procedure TThreadMessagesBase.Execute;
const
CWaitAll= False;
CTimeout= 60*60*1000;
var
Done: Boolean;
SynchroObjectCount: integer;
SynchroObjectHandles: THandleArray;
SynchroObjectHandlesPtr: Pointer;
WaitResult: integer;
SynchroObjectIndex: integer;

procedure SetUpSynchroObjectHandleArray;
begin
inherited GetEventHandleArray(SynchroObjectHandles);
SynchroObjectCount:= Length(SynchroObjectHandles);
SynchroObjectHandlesPtr:= @SynchroObjectHandles[0];
end;

begin
{Create window to route thread messages to:}
CreateWindowObject;
try
{Create a thread message queue:}
CreateMessageQueue(Done);
if not Done then
raise Exception.Create('Unable to create thread message queue');
{Create list of SynchroObjects to be awaited:}
SetUpSynchroObjectHandleArray;
{Do thread loop}
repeat
{Wait for an event or a new message to be signalled}
WaitResult:= MsgWaitForMultipleObjects(SynchroObjectCount,
SynchroObjectHandlesPtr,
CWaitAll, CTimeOut, QS_ALLINPUT);
if WaitResult= WAIT_TIMEOUT then
begin
if Assigned(FOnBackgroundTask) then
Synchronize(DoBackgroundTask);
end
else
begin
SynchroObjectIndex:= WaitResult - WAIT_OBJECT_0;
if SynchroObjectIndex=0 then
HandleTerminateRequest
else if SynchroObjectIndex=1 then
HandleSuspendRequest
else if SynchroObjectIndex=2 then
HandleTerminateSelfRequest
else if SynchroObjectIndex=3 then
HandleSuspendSelfRequest
else if SynchroObjectIndex= SynchroObjectCount then
HandleMessage;
end;
until Terminated;
finally
DestroyWindowObject;
end;
end;

procedure TThreadMessagesBase.CreateMessageQueue(var Done: Boolean);
var
Msg: TMsg;
begin
Done:= PeekMessage(Msg, FWindowHandle, WM_APP, WM_APP, PM_NOREMOVE);
end;

procedure TThreadMessagesBase.HandleMessage;
var
Msg: TMsg;
begin
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;

Regards,

EM


Remy Lebeau (TeamB)

unread,
Jun 25, 2008, 1:22:46 PM6/25/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:48625a3f$1...@newsgroups.borland.com...

> How does the TWndClass.lpfnWndProc pointer relate to
> the pointer assigned to the window using SetWindowLong?

TWndClass.lpfnWndProc points to the default message handler that is assigned
to the HWND when it is first created. The SetWindowLong() call then
replaces the HWND's current handler with a new one, in this case the one
that MakeObjectInstance() allocated to route messages to the specified class
method.

> Below is a draft of a thread message handler class that uses the
> alternative approach to creating a window object associated with
> the thread. Any comments?

Was there something wrong with the code I gave you earlier? You are
duplicating what AllocateHWnd() already does for you. Not only that, but
you are re-registering your custom window class on every thread. You do not
need to do that every time, as I explained earlier.

> Windows.UnregisterClass(WindowClassname, HInstance);
> raise Exception.Create('Error in RegisterWindowClass');

I suspect you meant to check the Result of UnregisterClass() first?
Otherwise, secondary instances of your thread would always raise the
exception.

> {Get pointer to WndProc method:}
> FWndProcPtr := MakeObjectInstance(WndProc);

I would suggest moving that into the constructor instead...

> {Release memory created for WndProc:}
> FreeObjectInstance(FWndProcPtr);

... and that to the destructor.

> SynchroObjectIndex:= WaitResult - WAIT_OBJECT_0;
> if SynchroObjectIndex=0 then
> HandleTerminateRequest
> else if SynchroObjectIndex=1 then
> HandleSuspendRequest
> else if SynchroObjectIndex=2 then
> HandleTerminateSelfRequest
> else if SynchroObjectIndex=3 then
> HandleSuspendSelfRequest
> else if SynchroObjectIndex= SynchroObjectCount then
> HandleMessage;

Use a case statement to clean up that code:

case WaitResult - WAIT_OBJECT_0 of
0: HandleTerminateRequest;
1: HandleSuspendRequest;
2: HandleTerminateSelfRequest;
3: HandleSuspendSelfRequest
else
if SynchroObjectIndex = SynchroObjectCount then HandleMessage;
end;

> finally
> DestroyWindowObject;
> end;

I would suggest overriding the thread's DoTerminate() method for that
instead of using a try..finally block.


Gambit


Enquiring Mind

unread,
Jun 26, 2008, 7:17:02 AM6/26/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:48627f2e$1...@newsgroups.borland.com...

> TWndClass.lpfnWndProc points to the default message handler that is
> assigned to the HWND when it is first created. The SetWindowLong() call
> then replaces the HWND's current handler with a new one, in this case the
> one that MakeObjectInstance() allocated to route messages to the specified
> class method.
>
Thanks, now I think understand! TWndClass.lpfnWndProc is used to initialize
the WndProc address of a newly created system window object. SetWindowLong
updates the WndProc pointer of the system window object, but not that of the
window class object, which, as you point out, is shared by all instances
window object class, and therefore cannot be instance-dependent.

> Was there something wrong with the code I gave you earlier?

The code you gave me was great, because it opened my eyes to the complex
issues that need to be taken care of, and illustrated a perfectly workable
solution to them. The only comments I would make about the code are:

a) the AllocateHWnd(nil) hack depends on knowledge of the inner workings of
AllocateHWnd procedure. This makes the code difficult to understand, and if
the AllocateHwnd procedure implementation were to change in the future the
hack might no longer work. It seems safer to use the API functions directly.

b) putting the procedure MakeObjectInstance in the constructor of the
TThread object and FreeObjectInstance in the destructor doesn't guarantee
that they will be called from the main thread - it seems safer to call them
within a Synchronize procedure.

c) the symmetry that pairing an AllocateHWnd call with a DeallocateHWnd call
is missing because as you pointed out one cannot call DeallocateHWnd - one
has to call DestroyWindow instead. To my mind pairing a CreateWindow call
with a DestroyWindow call better expresses the symmetry of the operation.

d) again for reasons of symmetry, I prefer to see the DestroyWindow call in
the same context as the CreateWindow call (i.e. in the Execute procedure),
rather than in the DoTerminate method.

e) when using AllocateHWnd procedure to create a window for a thread object
in a situation in which the application contains multiple classes descending
from TThread, each different thread class will be assigned the same window
class name 'TPUtilWindow' by AllocateHWnd. I would prefer each class
descending from TThread to have a unique window class name associated with
it, based on the thread class's own name. Thus if the application contains
thread classes TThreadA and TThreadB, the window class names for these
should be 'TThreadAWindow' and 'TThreadBWindow', rather than 'TPUtilWindow'.

These comments are mainly questions of style and personal preference rather
than substance, though!

> You are duplicating what AllocateHWnd() already does for you. Not only
> that, but you are re-registering your custom window class on every thread.
> You do not need to do that every time, as I explained earlier.
>

Regarding the registration of window classes, my intention was to maintain
an instance count for a given TThread-derived class (e.g. in a global
variable, or in a class field in more up-to-date versions of Delphi), and
only register the window class the first time an instance is created, and
only unregister it when the instance count returns to zero. Thus whenever
the instance count is greater than 0, the window class is not re-registered.

>> Windows.UnregisterClass(WindowClassname, HInstance);
>> raise Exception.Create('Error in RegisterWindowClass');
>
> I suspect you meant to check the Result of UnregisterClass() first?
> Otherwise, secondary instances of your thread would always raise the
> exception.

The nested procedure RegisterWindowClass is only called when the instance
count of the thread class is zero, implying that at that stage a registered
window class should not exist for the thread class. The exception is raised
to alert the developer that there's a design-time error in the code - for
example a failure to unregister the window class when the instance count
drops to zero. The exception call could be removed once the code has been
debugged.

>
> Use a case statement to clean up that code:
>
> case WaitResult - WAIT_OBJECT_0 of
> 0: HandleTerminateRequest;
> 1: HandleSuspendRequest;
> 2: HandleTerminateSelfRequest;
> 3: HandleSuspendSelfRequest
> else
> if SynchroObjectIndex = SynchroObjectCount then HandleMessage;
> end;
>
>

The case statement would improve the legibility considerably, I agree, but
not necessarily the performance. I was a little disconcerted, in fact, to
read somewhere in the D7 help files that the Delphi compiler translates a
case statement into a chain of nested if statements as used in my code. I
had once believed that Pascal compilers translate a case statement into a
jump table, which improves performance because the execution path jumps
directly from the case statement to the block corresponding the case value,
without having to go through a series of if statement tests. Granted that in
the days of fast processors and OOP this kind or performance advantage is
not particularly significant, but the jump table concept was one of the
features of Pascal that made the language stand out from the crowd.

Regards,

EM


Remy Lebeau (TeamB)

unread,
Jun 26, 2008, 3:36:42 PM6/26/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:48637ab0$1...@newsgroups.borland.com...

> putting the procedure MakeObjectInstance in the constructor of
> the TThread object and FreeObjectInstance in the destructor
> doesn't guarantee that they will be called from the main thread

If the main thread creates and destroyes the thread, then they would be.

> it seems safer to call them within a Synchronize procedure.

Synchronize() would only work if a VCL message pump were running in the main
thread. If your thread were running inside a DLL, for instance, then you
would not have that guarantee anymore.

> the symmetry that pairing an AllocateHWnd call with a DeallocateHWnd
> call is missing because as you pointed out one cannot call DeallocateHWnd
> - one has to call DestroyWindow instead.

That is not what I said. I said that DeallocateHWnd() could not be called
from a different thread context than the one that called AllocateHwnd(). I
did not say that DeallocateHWnd() could not be called at all.

> d) again for reasons of symmetry, I prefer to see the DestroyWindow
> call in the same context as the CreateWindow call (i.e. in the Execute
> procedure), rather than in the DoTerminate method.

DoTerminate() is called in the same thread context as Execute().
DoTerminate() is good place to perform operations that have to be done
regardless of how Execute() exits - whether normally or via an uncaught
exception. That way, you don't need a try..finally inside of Execute().

> when using AllocateHWnd procedure to create a window for a thread
> object in a situation in which the application contains multiple classes
> descending from TThread, each different thread class will be assigned
> the same window class name 'TPUtilWindow' by AllocateHWnd. I
> would prefer each class descending from TThread to have a unique
> window class name associated with it, based on the thread class's own
> name. Thus if the application contains thread classes TThreadA and
> TThreadB, the window class names for these should be 'TThreadAWindow'
> and 'TThreadBWindow', rather than 'TPUtilWindow'.

Why, though? That just causes redundant window classes to be registered
that all contain the same initialization data. You are creating a HWND
whose sole purpose is to call a thread's WndProc() method and nothing else.
You don't need to define multiple custom window classes to accomplish the
same thing that 'TPUtilWindow' already does.


Gambit


Enquiring Mind

unread,
Jun 27, 2008, 6:39:07 AM6/27/08
to

"Remy Lebeau (TeamB)" <no....@no.spam.com> wrote in message
news:4863f429$1...@newsgroups.borland.com...

>
>
>> it seems safer to call them within a Synchronize procedure.
>
> Synchronize() would only work if a VCL message pump were running in the
> main thread. If your thread were running inside a DLL, for instance, then
> you would not have that guarantee anymore.
>
Interesting point. Presumably a DLL doesn't have any primary thread of its
own, because it does not have an executable "main" procedure. So presumably
any running DLL routine inherits the thread context of the routine that
called it, and so on up the chain of routine calls. If a thread declared in
a DLL is called directly or indirectly by a GUI application, then the method
passed to the Synchronize method will presumably be queued to the message
queue of the application's primary thread.

Another situation requiring special consideration is that of a secondary
thread object contained within a console application. Although a console
application does not have a message loop created by Delphi to handle GUI
messages, presumably it still has a primary thread - that of the system
process it is running as. If the main procedure of the console application
directly or indirectly contains a call to PeekMessage or GetMessage, then
possibly Synchronize might work.

> That is not what I said. I said that DeallocateHWnd() could not be called
> from a different thread context than the one that called AllocateHwnd().
> I did not say that DeallocateHWnd() could not be called at all.
>

The point I thought you were making is that it's not safe to call
DeallocateHWnd() within a secondary thread because DeallocateHWnd internally
calls FreeObjectInstance, which is not thread-safe.

> Why, though? That just causes redundant window classes to be registered
> that all contain the same initialization data. You are creating a HWND
> whose sole purpose is to call a thread's WndProc() method and nothing
> else. You don't need to define multiple custom window classes to
> accomplish the same thing that 'TPUtilWindow' already does.
>

Thanks for that - it makes what's going on much clearer. The purpose of the
registered window class record is simply to provide a set of initialization
parameters to a newly created system window object. It is only used when the
window object is created. For any non-visual window one can use the
initialization parameters contained in the UtilWindowClass global variable
of the Classes unit. The only non-null parameter is the field lpfnWndProc:
@DefWindowProc, which points to an API function anyway ( so why does the
application need to supply this to the system?); the field lpszClassName:
'TPUtilWindow' is not an initialization parameter - it's just a name by
which to refer to the registered window class record.

If a Delphi application instance registers a window class record named
'TPUtilWindow' , does it clean up by unregistering the record just before
the application instance terminates, or is the record just left in the
system for possible use by future application instances?

Regards,

EM


Remy Lebeau (TeamB)

unread,
Jun 27, 2008, 12:27:34 PM6/27/08
to

"Enquiring Mind" <Enquiri...@nospam.btopenworld.com> wrote in message
news:4864...@newsgroups.borland.com...

> Interesting point. Presumably a DLL doesn't have any primary thread
> of its own, because it does not have an executable "main" procedure.
> So presumably any running DLL routine inherits the thread context of
> the routine that called it, and so on up the chain of routine calls. If a
> thread declared in a DLL is called directly or indirectly by a GUI
> application, then the method passed to the Synchronize method will
> presumably be queued to the message queue of the application's
> primary thread.

The point I was trying to make is that you are not guaranteed that the main
.exe would always be a VCL app to begin with, thus the message could not be
handled correctly. In fact, it probably wouldn't work in a VCL app, either,
without extra setup on your part to initialize the DLL's TApplication object
with a valid window that Synchronize() can post messages to.

> Another situation requiring special consideration is that of a secondary
> thread object contained within a console application. Although a
> console application does not have a message loop created by Delphi
> to handle GUI messages, presumably it still has a primary thread - that
> of the system process it is running as.

Yes, but without a message pump in the main thread, the Synchronize()
request would not be processed at all, and your thread would become
deadlocked.

> If the main procedure of the console application directly or indirectly
> contains a call to PeekMessage or GetMessage, then possibly
> Synchronize might work.

Only if the TApplication object has a window allocated with which to receive
messages. Synchronize() posts a message to the TApplication.Handle window.

> The only non-null parameter is the field lpfnWndProc: @DefWindowProc,
> which points to an API function anyway ( so why does the application need
> to supply this to the system?)

Because a window can't have a nil pointer assigned for its message
procedure, but most every window needs to call DefWindowProc() at some point
so the OS can handle its own provate messages and such.

> If a Delphi application instance registers a window class record
> named 'TPUtilWindow' , does it clean up by unregistering the record
> just before the application instance terminates

It doesn't need to. The OS handles that automatically when the process
terminates.

> or is the record just left in the system for possible use by future
> application instances?

Window class registrations are local to each process. They don't carry over
from one application instance to the next.


Gambit


0 new messages