![]() |
SkyOS: Message Handling in C++ |
Having used the Windows
Template Library (WTL) for some years now, I thought it would be interesting
to try and apply some of the same ideas to SkyOS. I decided to start with
some simple message handling to see if this would work, and came up with a design
concept that features a class template and a set of preprocessor macros. Each
unique window is based on the class template, and uses the
windowData field in the
s_window structure to store a pointer
to the window object; the generic window procedure then extracts this pointer and
invokes the appropriate object method. To avoid a lot of excess code, a set
of preprocessor macros tidy up all the method handling and make them readable.
The window class template (Window) is a "mix-in" class that provides some basic functionality common to all windows. Currently, the Window class:
defines a generic window procedure (WindowProc)
that is shared by all instances of the window class;
manages a cached GC
(m_pGC) for drawing;
stores a pointer to the s_window
structure (m_pWindow);
provides utility functions—such as
GetWidth() and
GetHeight()—that are wrappers around SkyOS
API calls.
The header file for the
Window class is a fairly generic class declaration,
except for the WindowProc() function,
which looks like this:
|
|
The 'magic' occurs in the line that reads T*
pT = *(reinterpret_cast<T**>(pw->windowData));
which extracts a pointer from the windowData
field of the s_window structure and casts
it to the appropriate window subclass. Because this is a template, this cast
can be done before the window subclass is declared, and any undefined references
will generate errors at link time; more importantly, all subclasses can safely share
the same WindowProc code.. This
is the same technique that the WTL uses, and is explained well in a
tutorial by Michael
Dunn; for now, just take it on faith that this generates relatively compact
code with minimal overhead.
The same header file also defines a set of preprocessor macros that can be used to easily code message handlers for window classes. These are simplified versions of the WTL macros, and are as follows:
BEGIN_MSG_MAP(classname) declared
at the start of a 'message map' and includes the class name for debugging
purposes;END_MSG_MAP declared at the end
of a 'message map';MAP_CMD(id,fn) connects a specific
command (MSG_COMMAND) to a class method,
for example MAP_CMD(ID_APP_EXIT,onAppExit);MAP_CMD_RANGE(idLo,idHi,fn) connects
a range of commands to a class method, for example
MAP_CMD_RANGE(ID_FILE_MRU1,ID_FILE_MRU8,onFileMRU);MAP_MSG(id,fn) connects a specific
message to a class method, for example MAP_MSG(MSG_GUI_REDRAW,onRedraw);MAP_MSG_RANGE(idLo,idHi,fn) connects
a range of messages to a class method, for example
MAP_MSG_RANGE(WM_WG_RANGE_POPUP,WM_WG_RANGE_POPUP+100,onRangePopUp);Unlike their WTL inspiration, which uses 'message cracking' to send message-specific
parameters to each function, this version uses a simple 'one size fits all' approach:
every command handler must be prototyped as
bool myCmdFunc(unsigned
int, HRESULT &), while every message
handler must be prototyped as
bool myMsgFunc(s_gi_msg
const &, HRESULT &).
If the function handles the command, i.e., no further processing is necessary, it
should return true; returning a false result
will cause the message to be handed off to the default window procedure. The
function can also indicate success or failure by changing the HRESULT
parameter, which defaults to S_OK (success).
To see how this Window template class is put into use, let's look at a subclass
called WSplash (WSplash.h,
WSplash.cpp). The class declaration begins with
class WSplash
: public Window<WSplash>
which declares WSplash as a subclass of
Window, and automagically gives embodies
WSplash with all the variables and methods
belonging to the Window template class.
The window subclass also defines a message map that connects the various methods
to the commands and messages:
|
|
In this particular example, all MSG_GUI_REDRAW
messages will be routed to the WSplash::onRedraw()
method; if that method isn't overridden by the WSplash
subclass, they will be sent to Window::onRedraw()
instead. Likewise, the ID_APP_EXIT
command is automatically routed to the onAppExit()
method. Keep in mind that a window subclass does not need to include all
commands and messages in the message map, only those that it is interested in.
The others will be handled by the Window
class, or passed to the default window procedure. The only methods that every
window subclass should implement are the Create(),
Destroy(), and
Draw() methods. The Create()
method actually creates the window, and must also store a pointer to itself in the
windowData field. This can be done
by passing address of this to the
GI_create_window() call as follows (code omitted
for clarity):
|
|
Both Create() and
Destroy() must invoke the base class method
as well. The base_class typedef can be
used to explicitly access the desired method of the Window class;
writing return
Window<WSplash>::Create();
would also be correct, but not as easy to understand. As one would expect, the
Destroy() method is called when the window
is being destroyed, while the Draw() method is invoked to draw the
contents of the window client.
So far, this technique seems to be working fine, and make window management a lot more streamlined to both code and debug. Nonetheless, this is very much a 'work in progress', so please feel free to suggest improvements or alternatives. Stay tuned...
|
|
2004.08.09-11:27