Introduction
I created a dialog based app to run on Windows wih MFC. The heart of this program was a few classes that didn't depend on MFC and I took care to make them easily portable, because I knew I wanted to try it on CE. I started Embedded Visual C 3.0 and looked for an App Wizard to help me create a Win 32 based dialog app. Since there was none to be found, I created a new project using the Win32 App Wizard. Just like the Win32 App Wizard in Visual Studio, there wasn't a class in sight.
I was about to suffer through the lack of objects when I happened to see an article by Steve Hanov in the C/C++ Users Journal that explained wrapping Windows with a class. So I set out to make 2 starter projects. The first a Win 32 "Hello World" app wrapped in an object and a Win 32 Dialog app wrapped in an object.
In this article I take the standard Windows CE "Hello World" Win 32 application and wrap it with a class that can be inherited from.
The Windows callback problem
The root problem in creating a wrapper is the Windows Callback function. This function must be a static procedure so the address can be supplied to the RegisterClassEx
function. To solve this the function must be a callback procedure.
LRESULT CALLBACK CBaseWindow::BaseWndProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam )
Now that the Windows Procedure is part of a class, the problem is getting to the other members the class. Static member functions can't access non-static member functions without a this
pointer. To supply the needed pointer to the window it can be included in the CREATESTRUCT
. The structure is automatically filled in when you make the CreateWindow
call (note the this
pointer in the final argument).
hWnd = CreateWindow( szWindowClass, szTitle, WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, (void *)this );
This CREATESTRUCT
is availible in the callback procedure during the WM_CREATE
message as the lParam
. The pointer is retrieved from the structure and stored as user data using the Windows function SetWindowLong
.
LRESULT CALLBACK CBaseWindow::BaseWndProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam )
{
if ( msg == WM_CREATE )
{
CBaseWindow *pObj = reinterpret_cast<CBaseWindow *>
((long)((LPCREATESTRUCT)lParam)->lpCreateParams);
::SetWindowLong( hwnd, GWL_USERDATA,
(LONG)((LPCREATESTRUCT)lParam)->lpCreateParams );
. . .
On all subsequent messages from this window, a call to GetWindowLong
will retrieve the this
pointer. Now you are free to access your entire class.
This method can be used when creating Dialog boxes also. In the case of a modal dialog box, the windows callback procedure is specified in the DialogBox
call. To get the object pointer to the callback procedure use DialogBoxParam
. The object pointer will be the final parameter.
DialogBoxParam( GetInstance(), (LPCTSTR)IDD_ABOUTBOX, hWnd,
(DLGPROC)pAboutWindow->BaseWndProc, (long)pAboutWindow );
To retrieve the pointer, simply grab the lParam
of the call to the base window procedure during the WM_INITDIALOG
message. Now save the pointer as you did before.
. . .
if ( msg == WM_INITDIALOG )
{
CBaseWindow *pObj = reinterpret_cast<CBaseWindow *>(lParam);
::SetWindowLong( hwnd, GWL_USERDATA, lParam );
. . .
A difference between a desktop version of Windows and the CE version is in the window creation process. On the desktop WM_NCCREATE
is the first message so the this
pointer should be captured during the its processing. In Windows CE this message doesn't exist. The first message is WM_CREATE
so it must be used.
Although this method is a little complex, it saves declaring static data structures that map window handles to pointers that point to the correct objects. As each window is created, it stores its own pointer to the object that owns it.
Inheritance
Most windows are created as more specialized cases of existing windows. Using C++, objects can utilize this relationship. The base window class provides a static function that calls a virtual function. Here is a look at the base window class declaration:
class CBaseWindow
{
public:
CBaseWindow(){hInst=0;}
~CBaseWindow(){}
HWND hWnd; BOOL DlgFlag;
static LRESULT CALLBACK BaseWndProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam );
protected:
HINSTANCE hInst; HWND hwndCB;
HINSTANCE GetInstance () const { return hInst; }
virtual LRESULT WndProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam,
PBOOL pbProcessed )
{
*pbProcessed = FALSE; return NULL;
}
};
Here you see the static function BaseWndProc
that windows will use as a callback and the virtual function WndProc
that will get called as the message processor for the window. In the case of the function for the base window, WndProc
indicates that no messages are are processed and returns.
Here is where the fun begins. The Base Class can't be used for a window by itself, but each window in the program can now inherit the Base Class in the declaration of its own class. From here on out, all of the details of managing this
pointers to associate a code object to a window are hidden. Here the main window class inherits from the base class.
class CMainWindow : public CBaseWindow
{
public:
CMainWindow(){}
~CMainWindow(){}
BOOL InitInstance( HINSTANCE hInstance, int nCmdShow );
protected:
HWND CreateRpCommandBar( HWND );
ATOM MyRegisterClass( HINSTANCE, LPTSTR );
virtual LRESULT WndProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam,
PBOOL pbProcessed );
};
As the window needs become more specialized, you create a new class based on an old class and change the behaviors to suit the new requirements. For example, the WndProc
of the CMainWindow
class replaces the function by the same name in the CBaseWindow
class.
When you override the old function with a new virtual function you should call the inherited class first to get the inherited traits then change the processing in the way you need. Here the WndProc
of the main window calls the WndProc
of the CBaseWindow
class before doing its message processing.
LRESULT CMainWindow::WndProc( HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam,
PBOOL pbProcessed )
{
int wmId, wmEvent;
LRESULT lResult = CBaseWindow::WndProc( hWnd, message,
wParam, lParam,
pbProcessed );
switch ( message )
{
. . .
Windows vs Dialogs
Anyone who has looked at a standard Win32 program and has seen an ordinary Windows callback procedure knows to expect this line at the end of the case table.
lResult = DefWindowProc( hwnd, msg, wParam, lParam );
If the message is processed by one of the virtual functions in the chain then the function will return FALSE
and the DefWindowProc
will not be called.
Rules are a little different if dealing with a dialog box. The DefWindowProc
, if needed, is called automatically for a dialog. If the message is processed by one of the functions in the chain then TRUE
is returned.
This is an annoyance and a source of errors (IMHO). So I have attempted to level the window and dialog box playing field a little by compensating in my base class. To do this I set a flag to remind myself if this is for a dialog or not. This way all of the window callbacks can be coded the same. Here is the static base window callback function.
LRESULT CALLBACK CBaseWindow::BaseWndProc( HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam )
{
if ( msg == WM_CREATE )
{
CBaseWindow *pObj = reinterpret_cast<CBaseWindow *>
((long)((LPCREATESTRUCT)lParam)->lpCreateParams);
::SetWindowLong( hwnd, GWL_USERDATA,
(LONG)((LPCREATESTRUCT)lParam)->lpCreateParams );
pObj->hWnd = hwnd;
pObj->DlgFlag = FALSE;
if ( pObj->hInst == 0 )
pObj->hInst = MainWindow.hInst;
}
if ( msg == WM_INITDIALOG )
{
CBaseWindow *pObj = reinterpret_cast<CBaseWindow *>(lParam);
::SetWindowLong( hwnd, GWL_USERDATA, lParam );
pObj->hWnd = hwnd;
pObj->DlgFlag = TRUE;
pObj->hInst = MainWindow.hInst;
}
BOOL bProcessed = FALSE;
LRESULT lResult;
CBaseWindow *pObj = WinGetLong<CBaseWindow *>( hwnd, GWL_USERDATA );
if ( pObj )
lResult = pObj->WndProc( hwnd, msg, wParam, lParam, &bProcessed );
else
return ( pObj->DlgFlag ? FALSE : TRUE );
if ( pObj->DlgFlag )
return bProcessed; else
if ( !bProcessed )
lResult = DefWindowProc( hwnd, msg, wParam, lParam );
return lResult; }
Using the Base Window Procedure
To get the object registered as the callback function for a window, the BaseWndProc
is set as the Windows callback function pointer when the class is registered.
ATOM CMainWindow::MyRegisterClass( HINSTANCE hInstance,
LPTSTR szWindowClass )
{
WNDCLASS wc;
wc.lpfnWndProc = (WNDPROC)BaseWndProc;
. . .
To wrap a Dialog Box with an object, the BaseWndProc
is sent as the Windows callback function pointer in the call to DialogBoxParam
.
CAboutWindow *pAboutWindow = new( CAboutWindow );
DialogBoxParam( GetInstance(), (LPCTSTR)IDD_ABOUTBOX, hWnd,
(DLGPROC)pAboutWindow->BaseWndProc, (long)pAboutWindow );
delete pAboutWindow;
Conclusion
I've covered how to get the code for encapsulating Windows messages into objects at a basic level on a Windows CE device. Most of this should apply to the desktop versions of Windows also. This doesn't wrap everything into an object, but associating a window with an object is one of the harder and most common problems. As a result, I have the first starter application I wanted. A standard Windows CE "Hello World" Win 32 application wrapped in a class.
I plan to continue by telling how I used this project as the starting point to get a dialog based application.
One handy tip: Look for the Visual Studio Project Renamer by Niek Albers here on CodeProject. It works well on Embedded Visual C++ 3.0 projects also.
References
- Steve Hanov, A Lightweight Windows Wrapper, C/C++ Users Journal, Aug 2000 pg.26
- Reliable Software Website, http://www.relisoft.com/win32/index.htm, various win 32 tutorials.