Click here to Skip to main content
Click here to Skip to main content

Window Wrapper for a WinCE Win32 "Hello World" app

, 11 Jul 2002
Rate this:
Please Sign up or sign in to vote.
In this article, I take the standard Windows CE "Hello World" Win32 application and wrap it with a class that can be inherited from.

Screen Shot

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;          // The main window handle
   BOOL              DlgFlag;       // True if object is a dialog window

   static LRESULT CALLBACK BaseWndProc( HWND hwnd, UINT msg,
                                        WPARAM wParam, LPARAM lParam );

protected:
   HINSTANCE         hInst;         // The current instance
   HWND              hwndCB;        // The command bar handle

   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;

   // call the base class first
   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 )
{
   // check to see if a copy of the 'this' pointer needs to be saved
   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;

   // Retrieve the pointer
   CBaseWindow *pObj = WinGetLong<CBaseWindow *>( hwnd, GWL_USERDATA );

   // call the virtual window procedure for the object stored in the 
   // windows user data
   if ( pObj )
      lResult = pObj->WndProc( hwnd, msg, wParam, lParam, &bProcessed );
   else
      return ( pObj->DlgFlag ? FALSE : TRUE ); // message not processed


   if ( pObj->DlgFlag )
      return bProcessed;     // processing a dialog message return TRUE 
                             // if processed
   else
      if ( !bProcessed )
         // If message was unprocessed and not a dialog, send it back to Windows.
         lResult = DefWindowProc( hwnd, msg, wParam, lParam );

   return lResult;           // processing a window message return FALSE 
                             // if processed
}   /*   BaseWndProc   */

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

  1. Steve Hanov, A Lightweight Windows Wrapper, C/C++ Users Journal, Aug 2000 pg.26
  2. Reliable Software Website, http://www.relisoft.com/win32/index.htm, various win 32 tutorials.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Bradley Manske
Software Developer
United States United States
No Biography provided

Comments and Discussions

 
GeneralTitle not changed Pinmemberyulincat17-Oct-05 18:53 
GeneralSubclassing an edit control Pinmemberdavidward304-Jul-05 22:23 
Generalconfiguring pocket pc port usb Pinmembervicoman14-Aug-04 1:11 
GeneralFirst message PinmemberNoam3219-Jun-04 19:23 
GeneralIt works but... PinmemberAndreas Muegge18-Jun-04 3:06 
General'this' pointer storage PinsussBrian Reed30-Oct-03 11:26 
GeneralRe: 'this' pointer storage PinmemberManir7-Jul-04 19:17 
GeneralYou're dereferencing a null pointer PinsussAnonymous5-Dec-02 8:47 
GeneralRe: You're dereferencing a null pointer PinmemberBradley Manske6-Dec-02 19:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140827.1 | Last Updated 12 Jul 2002
Article Copyright 2002 by Bradley Manske
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid