/*
Form3.cpp -- Modularizing Code And Debugging With Log Files
In the last program in this series - Form2, we introduced child window controls and the switch
logic structure in the Window Procedure to map incomming Windows messages to the code which
handles each message. There can be a bit of a problem with this switch construct that is not
immediately apparent in the Form2 code. Let me elaborate. The Form2 example didn't really do
anything significant. If the reader is in engineering, science, business, finance, agriculture,
accounting, or myriads of other technical specialities, the algorithms to accomplish anything
significant can run into untold thousands of lines of code - almost no limit really. But if
you try to put all this code into case blocks in a switch construct you'll have a real mess.
Taken to the point of absurdity, it's possible, for example, to end up with a five thousand line
program with only two procedures, i.e., about 25 lines of code in WinMain() and 4,975 lines of
code in the Window Procedure! This isn't good. I'm sad to admit that my first Windows Api SDK
style programs were like that.
As a first cut at fixing it one might redesign the Window Procedure so that a seperate message
handling function is provided for each message the Window Procedure handles, and in that way
keep the actual message handling code out of the Window Procedure's switch construct. For
example, when the Window Procedure receives a WM_CREATE message, under the case WM_CREATE,
call....
fnWndProc_OnCreate(hWnd, msg, wParam, lParam);
...and simply pass the four Window Procedure's parameters through to the new message handling
function. Likewise for all the other messages. See the code below to get the idea. I believe
you'll find this works out better.
Next thing - this app violates my mantra of 'no global variables', at least if #Debug is defined.
See the code below. Logging the values variables are taking on in a 'trace file' along with
output statements indicating where programm execution is taking place can be a valuable learning
and debugging tool. If #Debug isn't commented out, an Output.txt log file is created in the
app's directory. Here is that output from a program run where I entered something or other in
the edit control, clicked the button, then ended the program run...
Entering WinMain()
Right Before CreateWindow() Call In WinMain() - << Text From WinMain()...
Entering fnWndProc_OnCreate()
hWnd = 0x00000000000904E6
Leaving fnWndProc_OnCreate()
hWnd = 0x00000000000904E6 - << Text From WinMain()
Right After CreateWindow() Call In WinMain() - << Text From WinMain()
Entering fnWndProc_OnCommand()
LOWORD(wParam) = 1500
Leaving fnWndProc_OnCreate()
Entering fnWndProc_OnClose()
hWnd = 0x00000000000904E6
Entering fnWndProc_OnDestroy()
hWnd = 0x00000000000904E6
Leaving fnWndProc_OnDestroy()
Leaving fnWndProc_OnClose()
Leaving WinMain()
Note carefully that at the point of the CreateWindowEx() call in WinMain() to create the app's
main program window, we get the debug output that code execution is in fnWndProc_OnCreate().
That function call is actually occurring 'inside' the CreateWindowEx() call! What you are seeing
here is a C based object Constructor call. In fnWndProc_OnCreate() we get the main app's HWND
before it is revealed to us by the #Debug output statement in WinMain(). With this logic we see
the symmetric C based object Destructor call in fnWndProc_OnDestroy().
Next, note I added a WM_CLOSE handler to what had been our Form2 program. In fnWndProc_OnClose()
I call Api function DestroyWindow(), which causes Windows to send a WM_DESTROY message. The
debug output shows entry into the WM_CLOSE handler, then entry into the WM_DESTROY handler and
exit from this latter handler, followed finally by exit from the WM_CLOSE handler. Without a log
or trace file this behavior might not be obvious. And it is interesting.
Finally, you are seeing a truely elegant code design here in terms of the Windows Api. One
function - CreateWindowEx(), is used to create every type of window Windows supports. In
WinMain() we Register a 'custom' Window Class named "Form3" with which we base our main program
window. We feed that name into the CreateWindowEx() call in its second parameter - szClassName.
In fnWndProc_OnCreate() we see two more CreateWindowEx() calls, but neither of those calls
contain in the second parameter a Class Name we Registered in the app. The 1st CreateWindowEx()
call contains in its 2nd parameter the string "edit". The 2nd CreateWindowEx() call asks for
the instantiation of an object of class "button". We never Registered an "edit" or "button"
object in the "Form2" app. Think about it. What's going on here?
What's going on here is that Windows the operating system has predefined classes of specialized
'window' objects that can be instantiated in application programs by CreateWindow() or
CreateWindowEx() function calls. There are several categories of these objects or 'controls'.
Edit Controls and Button Controls are in the category known as the 'Standard Controls'. Then
there are the 'Common Controls'. These are a bit more complex to use and they are quite
sophisticated. Then there are ActiveX Controls. With those the CreateWindow() calls are actually
encapsulated within the control and the user creates them indirectly through the COM (Component
Object Model) subsystem. Finally, one can create controls oneself and these would be 'Custom
Controls' - a somewhat advanced topic, as are ActiveX Controls. But the truly elegant part of
this code design of the Windows Api is that one function call - CreateWindow(), is used to create
all these very different types of windows. Below is Form3.cpp....
*/
// cl Form3.cpp /O1 /Os /link Kernel32.lib User32.lib // 88,064 Bytes
// g++ Form3.cpp -luser32 -lkernel32 -oForm3_gcc.exe -mwindows -m64 -s -Os // 17,920 Bytes
// cl Form3.cpp /O1 /Os /GS- /Gy /link TCLib.lib kernel32.lib user32.lib // 4,608 Bytes
//#define TCLib
//#define Debug
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#ifdef TCLib
#include "stdio.h"
#else
#include <cstdio>
#endif
#define IDC_BUTTON 1500
#define IDC_EDIT 1505
#ifdef Debug
FILE* fp=NULL;
#endif
LRESULT CALLBACK fnWndProc_OnCreate(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
HINSTANCE hIns = NULL; // This is a C - based Constructor Function whose code execution will be occurring
// 'inside' the CreateWindowEx() call down in WinMain(). Unlike a C++ language
#ifdef Debug // Constructor, failure is a possibility. If -1 is returned by this function, the
fprintf(fp," Entering fnWndProc_OnCreate()\n"); // CreateWindowEx() function in WinMain() will fail and return
fprintf(fp," hWnd = 0x%p\n",hWnd); // a NULL for its HWND. Just below we'll make two CreateWindowEx()
#endif // calls for two 'Child Window Controls'. The code just at left
hIns=((LPCREATESTRUCT)lParam)->hInstance; // retrieves the HINSTANCE from the CREATESTRUCT pointed to by LPARAM.
CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE,50,40,200,25,hWnd,(HMENU)IDC_EDIT,hIns,0);
CreateWindowEx(0,L"button",L"Retrieve Text",WS_CHILD|WS_VISIBLE,95,90,120,30,hWnd,(HMENU)IDC_BUTTON,hIns,0);
#ifdef Debug
fprintf(fp," Leaving fnWndProc_OnCreate()\n\n");
#endif
return 0;
}
LRESULT CALLBACK fnWndProc_OnCommand(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
wchar_t szBuffer[256]; // Child Window Controls communicate with their parent
// by sending WM_COMMAND messages. Packed tightly within
if(LOWORD(wParam)==IDC_BUTTON && HIWORD(wParam)==BN_CLICKED) // the WPARAM and LPAREM parameters is information the
{ // parent (our main app window) can use to determine which
#ifdef Debug // child window control is sending the message, and what
fprintf(fp," Entering fnWndProc_OnCommand()\n"); // the nature of the user's interaction with the child
fprintf(fp," LOWORD(wParam) = %u\n",LOWORD(wParam)); // window control was that triggered the sending of the
#endif // message, e.g., the user clicked a button or typed a
GetWindowText(GetDlgItem(hWnd,IDC_EDIT),szBuffer,256); // character in an edit control. Note that the GetDlgItem()
MessageBox(hWnd,szBuffer,L"Button Click",MB_OK); // function returns the HWND of a child window given the
#ifdef Debug // HWND of the parent and the Control ID of the child window
fprintf(fp," Leaving fnWndProc_OnCreate()\n\n"); // control as specified in the HMENU parameter of the
#endif // CreateWindow() call that created the control. Its never
} // necessary to use global variables in Windows programs to
// persist HWNDs because Windows will always be able to
return 0; // return these to us through a GetDlgItem() function call.
} // Windows keeps track of all sorts of stuff like this.
LRESULT CALLBACK fnWndProc_OnClose(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
#ifdef Debug // The WM_CLOSE message provides the application the opportunity
fprintf(fp," Entering fnWndProc_OnClose()\n"); // of querying the user for confirmation that the user indeed
fprintf(fp," hWnd = 0x%p\n",hWnd); // wants to exit the application. In this app all I did with the
#endif // message was call DestroyWindow(). Note the Window Procedure
DestroyWindow(hWnd); // is 'reentrant'. fnWndProc_OnDestroy() will execute within
#ifdef Debug // fnWndProc_OnClose(). This is clearly seen in the Output.txt
fprintf(fp," Leaving fnWndProc_OnClose()\n"); // log file if #Debug is active/defined, i.e., not commented out.
#endif
return 0;
}
LRESULT CALLBACK fnWndProc_OnDestroy(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
#ifdef Debug // This is a C - Based Destructor Call for our main program
fprintf(fp," Entering fnWndProc_OnDestroy()\n"); // window. In more complex programs than this, it is a good
fprintf(fp," hWnd = 0x%p\n",hWnd); // place to make sure memory allocations are released, databases
#endif // or files are closed, etc. Here, all we need to do is call
PostQuitMessage(0); // PostQuitMessage() to stop the 'message pump' in WinMain()
#ifdef Debug // from running so that the program can end.
fprintf(fp," Leaving fnWndProc_OnDestroy()\n");
#endif
return 0;
}
LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) // I don't personally much care for placing a lot of code
{ // in 'case' constructs under a switch statement. So at
case WM_CREATE: // left you see I created seperate 'message handling functions'
return fnWndProc_OnCreate(hWnd, msg, wParam, lParam); // to put actual code to be executed for each message to be
case WM_COMMAND: // handled. The idea illustrated here is pretty simple; in my
return fnWndProc_OnCommand(hWnd, msg, wParam, lParam); // actual production code I don't use switch logic but rather
case WM_CLOSE: // for loop logic to iterate through a struct array containing
return fnWndProc_OnClose(hWnd, msg, wParam, lParam); // the message and a function pointer to the message handling
case WM_DESTROY: // function that handles that specific message.
return fnWndProc_OnDestroy(hWnd, msg, wParam, lParam);
}
return (DefWindowProc(hWnd, msg, wParam, lParam));
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPWSTR lpszArgument, int iShow)
{
wchar_t szClassName[] =L"Form3"; // Program Entry Point for Windows GUI app. I personally like to keep my
HWND hWnd = NULL; // WinMain() functions short. The only code I put in them is code to
WNDCLASSEX wc; // Register my main app window, the CreateWindowEx() call to create that window,
MSG messages; // and the 'Message Pump' that keeps the program alive and functioning. If
// the app instantiates multiple top level windows, I put the code to register
#ifdef Debug // those additional classes in the WM_CREATE handler of the main app's window.
fp=fopen("Output.txt","w"); // After all, that is the 'Constructor' for the entire app.
fprintf(fp,"Entering WinMain()\n");
#endif
memset(&wc,0,sizeof(wc)); // Zero out WNDCLASSEX object
wc.lpszClassName = szClassName; // Start filling out WNDCLASSEX object....
wc.lpfnWndProc = fnWndProc; // Function Pointer to Window Procedure
wc.cbSize = sizeof(WNDCLASSEX); // Size of WNDCLASSEX object
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; // Color for background of main app window
wc.hInstance = hInstance; // Virtual Memory Address Thing
RegisterClassEx(&wc); // Register Custom Window Class
#ifdef Debug
fprintf(fp," Right Before CreateWindow() Call In WinMain() - << Text From WinMain()...\n\n");
#endif
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,175,320,200,HWND_DESKTOP,0,hInstance,0); // C Based Object Constructor
#ifdef Debug
fprintf(fp," hWnd = 0x%p - << Text From WinMain()\n",hWnd);
fprintf(fp," Right After CreateWindow() Call In WinMain() - << Text From WinMain()\n\n");
#endif
ShowWindow(hWnd,iShow); // Show Window
while(GetMessage(&messages,NULL,0,0)) // Message Pump
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
#ifdef Debug
fprintf(fp,"Leaving WinMain()\n");
fclose(fp);
#endif
return (int)messages.wParam;
}