Now that you know how Delphi dispatches messages in windowed applications, it's time to trick Windows in thinking we have a window in a non-windowed application.
How to send messages to non-windowed applications
Isn't there a way to trick Windows in thinking we have a window? We could create one but having tens of windows displayed all across the screen just for the purpose of intercepting Windows messages is inaesthetical and unpractical. What if we could create a window but mark it in such way so it is not displayed on the screen? We could reach our goal of having a window procedure and at the same time don't fill the screen with dummy empty windows.
Again Delphi VCL addresses this very specific need by providing a convenient wrapper that handles all the low-level API calls. It provides two functions that must be used in pairs: AllocateHWND and DeallocateHWND. First one accepts a single parameter that is our window procedure and the second one does the cleanup when we no longer need the invisible window.
We have created a sample application that shows the usage of AllocateHWND/DeallocateHWND functions. To simulate a non-windowed application we will use a TThread descendent, defined as follows:
TTestThread = class(TThread)
private
FSignalShutdown: boolean;
{ hidden window handle }
FWinHandle: HWND;
protected
procedure Execute; override;
{ our window procedure }
procedure WndProc(var msg: TMessage);
public
constructor Create;
destructor Destroy; override;
procedure PrintMsg;
end;
|
Creating the hidden window
In the TTestThread constructor we create our hidden window which is destroyed in the TTestThread destructor:
constructor TTestThread.Create;
begin
FSignalShutdown := False;
{
create the hidden window, store it's
handle and change the default window
procedure provided by Windows with our
window procedure
}
FWinHandle := AllocateHWND(WndProc);
inherited Create(False);
end;
destructor TTestThread.Destroy;
begin
{ destroy the hidden window
and free up memory }
DeallocateHWnd(FWinHandle);
inherited;
end;
|
To test for the message routing we use a simple boolean (FSignalShutdown) that we initially set to False in the constructor and will hopefully be set to True in the window procedure (WndProc) upon receiving our message.
The window procedure
In order to keep things simple we have implemented a bare-bone window procedure as follows:
procedure TTestThread.WndProc(var msg: TMessage);
begin
if Msg.Msg = WM_SHUTDOWN_THREADS then
{
if the message id is WM_SHUTDOWN_THREADS
do our own processing
}
FSignalShutdown := True
else
{
for all other messages call
the default window procedure
}
Msg.Result := DefWindowProc(FWinHandle, Msg.Msg,
Msg.wParam, Msg.lParam);
end;
|
This procedure is called for each and every Windows message, so we need to filter out only those that are interesting, in this case WM_SHUTDOWN_THREADS. For all other messages that are not handled by our application remember to call the default Windows procedure. Even if this is not so important for our non-visible non-interactive window it is a good practice and missing this step may yield to unpredictable behaviors.
Message identifier?
If applications were to use arbitrary message id's their operation would soon interfere and messages meant for one application would be interpreted in unknown and unwanted ways by others.
To prevent this Windows provides a way for each application or related applications to obtain a unique message identifier. How? Find on the next page...
Next page > Obtaining a unique message identifier. The DeallocateHWND bug. > Page 1, 2, 3