http://www.codeproject.com/KB/winsdk/msgcrackwizard.aspx
Many beginner and intermediate programmers are often faced with the problem of spaghetti switch...case
code blocks when programming with the Windows API in C/C++. When you add a lot of messages to catch in your window procedure, looking where is your e.g., WM_COMMAND
or WM_CHAR
, the block of code becomes a real nightmare.
The problem of the thousand lines window procedure can be solved with a header file that is shipped since the days of the C/C++ 7.0 compiler and the Windows Software Development Kit for Windows 3.1. That header is <windowsx.h> and contains a lot of useful macros. According to Microsoft, the facilities of this header file can be resumed in the following groups:
STRICT
macro.Since Message Cracker Wizard is designed to aid with the message crackers, I will skip the other useful macros the header file makes available. If you are interested in a brief description of what you can do with the WINDOWSX.H file, you can look at the MS Knowledge Base Article #83456.
Well, let's introduce the advantages of the message crackers and, of course, why the tool offered here can be useful to work with them in your code.
When you are programming with the Win32 SDK, you process window and dialog messages with a window procedure, commonly named WndProc
. It is very common in Windows C programming that the window procedure catches every window message using a switch
keyword and a bunch of case
labels for every message we want to catch.
Suppose that we want to process WM_COMMAND
, WM_KEYUP
, WM_CLOSE
and WM_DESTROY
for our main window. We could write a window procedure with a logic like this:
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_COMMAND: // ... break; case WM_KEYUP: // ... break; case WM_CLOSE: // ... break; case WM_DESTROY: //... break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } }
This is the most used manner since Windows 1.0 days to process window messages, and surely, it works. But the problem is when you begin to add more and more complex features to your program, such as MDI, OLE, common controls, etc., and you get a thousand-lines window procedure. You begin to jump with PageDn and PageUp keys looking for a message you want to modify.
This is the first advantage of using message crackers: they convert that case label spaghetti in easy to maintain handling functions, like MFC.
And the second advantage is the proper parameter format you use in your handling functions. Instead of doing those switch(LOWORD(wparam))
, you can simply use switch(id)
because the message function that you provide passes id
as one of the "cracked" parameters, which equals to LOWORD(wparam)
.
The message handling macro HANDLE_MSG
is defined in windowsx.h, as follows:
#define HANDLE_MSG(hwnd, message, fn) \ case (message) : return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
As you may expect from the macro definition above, to convert your code to the "message-cracked" version, you must supply the cracking macro, HANDLE_MSG
, and the function to process that message. The HANDLE_MSG
macro goes into the window procedure. It needs three parameters: the window handle (hwnd
), the message you want to process (WM_xxxxx
), and the function we'll write to process that message. To better explain, the following code in the above window procedure converted to message crackers:
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG (hwnd, WM_COMMAND, OnCommand); HANDLE_MSG (hwnd, WM_KEYUP, OnKeyup); HANDLE_MSG (hwnd, WM_CLOSE, OnClose); HANDLE_MSG (hwnd, WM_DESTROY, OnDestroy); default: return DefWindowProc(hwnd, msg, wParam, lParam); } }
Wow! This is a better, compact and easily manageable window procedure. Now you would want to define your message processing functions (OnKeyUp, OnClose
, and OnDestroy
). This is a real advantage, as you can jump to your message processing function with the Visual Studio IDE:
The problem is that you must search in the definitions of WINDOWSX.H header and look for the parameters of the matching message processing function every time you add a message handler, because you can't use any parameters you want: the format of the handling function is explicit. Doing this repeated searching in the header file can become a tedious task and can lead to errors. The Message Cracker Wizard Tool comes to the rescue: it allows you to paste the correct function parameters for every message handler you want. And if you're writing from scratch, it can also write a template window or dialog procedure to begin with the window messages you will process.
Another useful feature in windowsx.h header is the possibility of message forwarding. This is used for "unpacking" the message processing function parameters into suitable WPARAM
and LPARAM
values to call another function that expects parameters such as PostMessage
, SendMessage
, CallWindowProc
, etc.
Suppose that we want to use SendMessage
to send a WM_COMMAND
message to a parent window that "simulates" the double-clicking of a control named IDC_USERCTL
by sending a notification code of BN_DBLCLK
. You would normally use:
SendMessage (hwndParent, WM_COMMAND, MAKEWPARAM(IDC_USERCTL, BN_DBLCLK), (LPARAM)GetDlgItem(hwnd, ID_USERCTL));
This is a rather complex syntax: the SendMessage
expects a WPARAM
parameter where the low-order word is the control ID and the high-order word is the notification code; and LPARAM
parameter is the handle to the control which we get here with the GetDlgItem
API.
The above code can be converted to WINDOWSX.H message forwarding macros, FORWARD_WM_xxxxx
. For each message, the forwarding macros use the same "packed" parameters as the message handling functions that Message Cracker Wizard creates, plus the function you want to call and pass the unpacked LPARAM
/WPARAM
s. For example, Message Cracker Wizard will generate the following function prototype for a WM_COMMAND
message and a myWnd
window ID:
void myWnd_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
Well, those cracked parameters are the same to be used by the forwarding macro -- so, as you may expect, the confusing SendMessage
call we showed above can be reduced to:
FORWARD_WM_COMMAND (hwndParent, IDC_USERCTL, GetDlgItem(hwnd, ID_USERCTL), BN_DBLCLK, SendMessage);
That's easy and works with all Message Cracker Wizard supported messages.
When you fire up the Message Cracker Wizard, its interface appears like the following:
The Wizard offers you all the messages handled by WINDOWSX.H in the top-left list box where you can click one or multiple messages. The Window ID edit box allows you to specify an identifier for the window you are specifying the message. Common IDs are MainWnd
, About
(for about dialogs), etc. This will appear in both the message handling function, in the HANDLE_MSG
macro, and in the name of the window/dialog procedure if you want to create one from scratch. The "Make Window Procedure" check box allows you to do that: create from scratch a window or dialog procedure with all the selected message cracker macros ready. Using this approach when beginning a Windows API project, you can cleanly write and organize your code, and of course, avoid mistakes. The two edit boxes at the bottom of the window will contain the generated code for the cracking macros and the functions to handle those messages (prototypes only). Note that the window procedure template code won't appear here when you check "Make Window Procedure": it will appear when you paste the code to your C++ editor only by clicking "Copy Macro".
To quickly tour the features of the Message Cracker Wizard Tool, let's do it by example. Remember that you must include the <windowsx.h> header with your project using the #include <windowsx.h>
directive in your .C / .CPP file.
Let's begin. Suppose you've already written your WinMain
basic code: you've successfully filled the WNDCLASS
structure, registered the window, and wrote a functioning message loop. Now you need a window procedure for your main window.
Open the Message Cracker Wizard. We need to select messages for our window, because MCW needs it to create our main window procedure from scratch. As you may know, it is very common for Windows programs to handle the WM_CLOSE
, WM_DESTROY
and WM_CREATE
messages, so let's build the window procedure with message crackers for those messages. After that, we'll also build the body of the message processing functions for that window procedure.
Select WM_CLOSE
, WM_DESTROY
and WM_CREATE
in the list box. As this window will be the main window of our program, go the Window ID and type main. This is a window ID that identifies your window/dialog and is put as suffix in cracking macros and processing functions. Of course, you'll want to maintain it consistent for all the message handling of a particular window. Look at the bottom edit boxes. They show the HANDLE_MSG
cracker macro and the related prototypes of the message processing functions.
But wait... we said that we want a ready window procedure. So click on 'Make Window Procedure' check box, and be sure that Window radio button is selected. Now we are ready. Keep in mind that Dialog works just like this, but modifies the procedure to be a dialog-type procedure.
First, we need the window procedure on our source code. Press on the 'Copy Macro' button (or press Ctrl-M), minimize the Wizard (or keep it at hand, since it remains top-most), go to your IDE and paste from the clipboard (Ctrl-V) in the place you want your window procedure. Voil�! You will get code like this:
// // main Window Procedure // LRESULT CALLBACK main_WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG (hwnd, WM_CLOSE, main_OnClose); HANDLE_MSG (hwnd, WM_CREATE, main_OnCreate); HANDLE_MSG (hwnd, WM_DESTROY, main_OnDestroy); //// TODO: Add window message crackers here... default: return DefWindowProc (hwnd, msg, wParam, lParam); } }
That's the window procedure with the three message cracking macros ready to work! And also, with a TODO comment to remember that you must add new message cracker macros there. Remember to unselect 'Make Window Procedure' checkbox when you want to add a HANDLE_MSG
macro to the window procedure.
But the code above does nothing, because we need the functions that process those three messages we want. Simply return to the Message Cracker Wizard tool and now click on 'Copy Function' button. Switch to your source code, locate your cursor where you want the functions bodies to be inserted, and paste with Ctrl+V or Edit/Paste menu. The wizard automatically creates the functions with the main Window ID and the correct parameters expected by the WINDOWSX.H header macros:
// // Process WM_CLOSE message for window/dialog: main // void main_OnClose(HWND hwnd) { // TODO: Add your message processing code here... } // // Process WM_CREATE message for window/dialog: main // BOOL main_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { // TODO: Add your message processing code here... } // // Process WM_DESTROY message for window/dialog: main // void main_OnDestroy(HWND hwnd) { // TODO: Add your message processing code here... }
The Wizard also automatically creates a heading comment and a TODO line to remind you to add code. Now you can add your message handling and processing logic easily and write complex window procedures. You can remove the comments if you want using the two checkboxes in the main window.
There are a few more features present in the program, which are rather intuitive.
This was a suggestion by some users of the program and it was implemented. Click on "Filters.." button (or press Ctrl+L) and you get the following dialog box. There you can select which messages appear listed on the listbox, classified on the type (this classification criteria was taken from Microsoft Spy++ utility).
Note that a present issue in v2.0 when using message filtering dialog is that the list box is filled up again when you click OK, so the previous selection is lost (this does not mean that your previous selected messages that appear on the target code window will disappear).
You may want to reduce the window size of the Message Cracker Wizard. This is possible by disabling "Show Target Code" option in the View menu (or by pressing Ctrl+F11). The main window will appear without the target code area:
Another feature that can be useful for low resolution displays or cluttered desktops is the window transparency feature. Click on the View menu, Window Transparency menu and select a transparency percentage (Solid is 100% opaque and 75% is 25% opaque). This feature is only available for Windows 2000/XP and Server 2003 users. On 9X OSes, only "Solid" option is available.
The Exclude Comments feature allows the code generator to exclude comments, either heading or "TODO" style commenting. Just select or unselect the checkboxes on the main window.
Finally, the Stay On Top feature is pretty self-descriptive.
The following features may appear in the following releases:
I hope this little tool to be of interest to any Windows SDK programmer and of course, to be a potential method to write cleaner Win32 API programs. I'm open to suggestions to improve the tool. If you find this program useful, mail me, because I will be very happy to listen to any good comment.
Thanks for all support!! You know who you are!
As always, check my home page where I mention the updates to this program.