5,662,937 members and growing! (24,007 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Libraries » General     Intermediate

A Minimal Windows Wrapper

By RandomMonkey

An overview of a basic Windows wrapper
C++, Windows, Visual Studio, MFC, Dev

Posted: 20 Feb 2006
Updated: 20 Feb 2006
Views: 17,988
Bookmarked: 10 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
4 votes for this Article.
Popularity: 2.66 Rating: 4.41 out of 5
0 votes, 0.0%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
1 vote, 25.0%
4
3 votes, 75.0%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Introduction

This article shows and discusses a minimal and functional Windows wrapper.  The wrapper presented in this article is the core upon which DWinLib is built, but shows nothing beyond that core.  This article is therefore only useful as an introduction to the inner workings of DWinLib, as well as showing how Windows may be wrapped.

The above zip file contains a 'Tic-Tac-Toe' game built with this bare wrapper.  The code in the zip file shows a method of extending the code presented in this article, but that method is not the method DWinLib took.

This article is meant to be read as part of the DWinLib tutorial of the above link, but will stand upon its own, and does not require other background material.

Other Wrappers

There are three other Windows API wrapping tutorials that I am aware of: Relisoft's, Oluseyi Sonaiya's, and David Nash's SDI Framework.  There is also the Win32 Generics wrapper, but that site does not appear to try to show you how it is accomplished.  In addition, it requires high-level template magic to accomplish its tricks.

The Differences

For those of you who have read the previously mentioned articles, DWinLib differs from them in insignificant to significant ways.  (For those who don't have any background creating Window wrappers, skim through and pick back up at the next section.)

DWinLib differs from all of the previous wrappers in the manner in which the window class instance is stored during the creation of the window.  There will be a little more on this later in the article.

Of the previously mentioned wrappers, DWinLib works in a manner that is most similar to Oluseyi's wrapper, but DWinLib uses the GetWindowLong/SetWindowLong approach to determining the appropriate window class to call for any given message, rather than searching a std::map.

DWinLib (and Oluseyi's method, for that matter) is much more flexible then Relisoft's method, in the fact that DWinLib does not require you to define handlers for each individual Windows message in the primary static window procedure.  Rather, you can override the entire non-static, member window procedure for a window, and add the desired functionality to the derived class without the parent class knowing anything about the additional messages you may be handling.  (It is easier to handle it other ways in DWinLib, but it *is* possible.)

(David Nash's method is also more flexible than Relisoft's method, but David's CWin::WndProc does not handle very many messages, meaning that you will have to look up many more WPARAM and LPARAM meanings each time you override that procedure.)

And compared to David Nash's SDI framework, DWinLib handles MDI applications.  DWinLib could be pared down to handle SDI applications, although this has not been needed so far, so it has not been done yet.

Wrapping a Window

The basic problem you will encounter when wrapping a Window procedure in an object oriented language is that of going from a static function to a non-static member function of an object.  This is necessitated by the fundamental design of Windows itself, and its 'C' legacy.  Windows is designed so that the function Windows calls in order to tell your program that things are happening must be a static, non-member function of your program.

Let us examine a window program that illustrates this difficulty, and see how it is solved in DWinLib.  The following is one of the simplest of window programs - it simply displays a window until the 'Close' button is pressed.  It doesn't use a wrapper of any nature.  You will note that I have outlined five distinct steps that take place in the code via comments.

#include <windows.h>


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hInst;

// **************************************

// Step 1: Create main program entrance:

// **************************************

int WINAPI WinMain(HINSTANCE instance, HINSTANCE , PSTR , int show) {
   // ***********************************

   // Step 2: Register the Window Class:

   // ***********************************

   static TCHAR appName[] = TEXT("MinApplication");
   hInst = instance;
   WNDCLASS wndclass;
   wndclass.style         = CS_HREDRAW | CS_VREDRAW;
   wndclass.lpfnWndProc   = WndProc;
   wndclass.cbClsExtra    = 0;
   wndclass.cbWndExtra    = 0;
   wndclass.hInstance     = instance;
   wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
   wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wndclass.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
   wndclass.lpszMenuName  = NULL;
   wndclass.lpszClassName = appName;

   if (!RegisterClass(&wndclass)) {
      MessageBox(NULL, TEXT("Oh no, Mr. Bill...."), appName, MB_ICONERROR);
      return 0;
      }

   // ***************************

   // Step 3: Create the window:

   // ***************************

   HWND hwnd = CreateWindowEx(WS_EX_APPWINDOW, appName, TEXT("MinApplication"),
               WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW | WS_BORDER,
               CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
               NULL, NULL, instance, NULL);

   ShowWindow(hwnd, show);
   UpdateWindow(hwnd);

   // ********************************

   // Step 4: Enter the message loop:

   // ********************************

   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
      }
   return msg.wParam;
   }


// ******************************************

// Step 5: Handle the messages from Windows:

// ******************************************

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
   switch (message) {
      case WM_DESTROY :
         PostQuitMessage(0);
         return 0;
      }
   return DefWindowProc(hwnd, message, wParam, lParam);
   }

One of the difficulties in programming a non-trivial application in the above manner is that a great deal of time is spent creating and managing the static WndProc procedures.  It is also necessary to tie your application's logic and data into the WndProc procedures, and this can be quite a task for a non-trivial application.

Below is the previous program re-written using the same basic mechanism that DWinLib uses.  It is almost four times larger than the preceding, code-wise, although it compiles to 52 Kb, vs. 48 Kb for the non-wrapped version.  It can be cut-and-pasted into a blank project and compiled as-is.

As you peruse the code, keep the following in mind:

The code works by creating a WinApp unit to hold the main static callback.  A WinBaseO unit is defined as a virtual base class from which to derive actual windows.  (No member function screams out to be made into a pure virtual, therefore only common sense is to keep you from instantiating a WinBaseO object.)

When a WinBaseO object is instantiated, it registers the fact that it is being instantiated with the WinApp from within the WinBaseO constructor.  The classes derived from WinBaseO override any WinBaseO functions required, in order to handle the Windows specific functions (WM_PAINT, etc.) in a manner you desire.  You can also add functions of your own to the derived classes, to handle your own logic unrelated to the window logic if you need to.

In this example, only a WinMainO unit is derived from WinBaseOWinMainO is a required unit within DWinLib applications, and contains the main window to your application.  In a non-trivial application you may have many more windows derived from WinBaseO.

All in all, this is a simple approach, and it works well.

You will note that the same five steps have been commented as were previously pointed out, although there are a couple more comments thrown in.

You will also note that the fifth step has been broken into three parts.  The first part (5 - just as in the previous code) is a static message handler that is part of the WinApp unit.  The second part (5a) is a non-static, member function of the WinBaseO unit, used to define default handlers for the Window messages.  The third part (5b) overrides any window message handlers for specific use by your window, when the default handler will not do the task you want it to.

The other comments are thrown in to indicate program structure unrelated to the outlined steps.

#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
#define _WIN32_IE 0x0400

#include <tchar.h>

#include <windows.h>

#include <string>


class WinBaseO;

// *************************************************************************

// Define a 'WinApp' class to handle the main static window procedure.

// There will only be one of these instantiated in the program as a global.

// *************************************************************************

class WinApp {
   private:
      static WinBaseO * winBeingCreatedC;
      HINSTANCE hInstC;
   public:
      WinApp(HINSTANCE hInst_, HINSTANCE, LPSTR, int);
 
      LRESULT static CALLBACK WindowProc(HWND window, unsigned int msg,
                  unsigned int wParam, long lParam);
      HINSTANCE hInst() { return hInstC; }
      void   setWinBeingCreated(WinBaseO * win) { winBeingCreatedC = win; }
      WPARAM run();
   };


// **********************************************************

// Define a base window class for deriving real windows from

// **********************************************************

class WinBaseO {
   //For dealing with the window handle and window procedure

      protected:
         HWND hwndC;
      public:
         WinBaseO();
         ~WinBaseO();
         
         //winProc can be overridden for each window, but it is easiest to just

         //override the individual functions.

         virtual LRESULT winProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam);
         HWND hwnd() { return hwndC; }
         
         void hwnd(HWND hwnd) { hwndC = hwnd; } //Only needed by WinApp::WindowProc


   //Routines to be inherited by derived windows

      public:
         virtual LRESULT wClose() { return DefWindowProc(hwndC, WM_CLOSE, 0, 0); }
   };


WinBaseO * WinApp::winBeingCreatedC = NULL;
WinApp * gWinApp; //A Global WinApp object for the entire program



// ************************************

// Now fill in the WinBaseO definition

// ************************************

WinBaseO::WinBaseO() : hwndC(NULL) {
   gWinApp->setWinBeingCreated(this);
   }


WinBaseO::~WinBaseO() {
   if (IsWindow(hwndC) == TRUE) DestroyWindow(hwndC);
   }
   
   
// ***************************************************************************

// Step 5a: Handle the messages from Windows in a non-static member function:

// ***************************************************************************

LRESULT WinBaseO::winProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
   switch (msg) {
      case WM_CLOSE:
         wClose();
         return 0;
      }
   return DefWindowProc(window, msg, wParam, lParam);
   }


// ***************************************************

// Now define and fill in the guts to a 'real' window

// that uses the above WinBaseO definitions.

// ***************************************************

class WinMainO : public WinBaseO {
   //Regular class stuff

      private:
         void registerWinClass();

      public:
         WinMainO();
         
   //WinBaseO overrides:

      public:
         virtual LRESULT wClose();
   };


namespace { //An unnamed namespace for this unit to use

   TCHAR * winCaption = TEXT("Bare Windows Wrapper Example");
   TCHAR * winClassName = TEXT("BareWindowApp");
   }


// ***********************************

// Step 2: Register the Window Class:

// ***********************************

void WinMainO::registerWinClass() {
   WNDCLASS wc;
   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = gWinApp->WindowProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = gWinApp->hInst();
   wc.hIcon         = NULL;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)GetStockObject (GRAY_BRUSH);
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = winClassName;
   if (!RegisterClass (&wc)) abort();
   }
 

WinMainO::WinMainO() {
   static bool registered = false;
   if (!registered) {
      registerWinClass();
      registered = true;
      }

   // ***************************

   // Step 3: Create the window:

   // ***************************

   hwndC = CreateWindow(winClassName, winCaption, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
               CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, gWinApp->hInst(), 
               NULL);
   if (!hwndC) abort();

   ShowWindow(hwndC, SW_SHOW);
   UpdateWindow(hwndC);
   }


// *************************************************

// Step 5b: Override any message handlers you want

// to handle differently than the WinBaseO handler:

// *************************************************

LRESULT WinMainO::wClose() {
   PostQuitMessage(0);
   return DefWindowProc(hwndC, WM_CLOSE, 0, 0);
   }
 

// **********************************

// Now fill in the WinApp definition

// **********************************

WinApp::WinApp(HINSTANCE hInst_, HINSTANCE, LPSTR, int) :
            hInstC(hInst_) {

   gWinApp = this;
   }
 

WPARAM WinApp::run() {
   WinMainO win;
   // ********************************

   // Step 4: Enter the message loop:

   // ********************************

   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
      }
   return msg.wParam;
   } 


// ******************************************

// Step 5: Handle the messages from Windows:

// ******************************************

LRESULT CALLBACK WinApp::WindowProc(HWND window, unsigned int msg, unsigned int wParam,
            long lParam) {

   WinBaseO * win = reinterpret_cast<WinBaseO*>(GetWindowLong(window, GWL_USERDATA));
   if (win) return win->winProc(window, msg, wParam, lParam);
   SetWindowLong(window, GWL_USERDATA, reinterpret_cast<long>(winBeingCreatedC));
   winBeingCreatedC->hwnd(window);
   return winBeingCreatedC->winProc(window, msg, wParam, lParam);
   }


// ***************************************************************

// And finally, a WinMain that takes advantage of the above work:

// ***************************************************************

// **************************************

// Step 1: Create main program entrance:

// **************************************

int WINAPI WinMain(HINSTANCE inst1, HINSTANCE inst2, LPSTR str, int show) {
   WinApp app(inst1, inst2, str, show);
   return app.run();
   }

The above code has been kept to a minimal state, in that error handling is very minimal, and all extraneous messages are ignored.  The previous code is simply enough to show how a basic Windows wrapper can be accomplished.

Earlier I stated that there would be more information on how the window class instance is stored during the creation of the window.  As I stated before the second code block, the WinBaseO's constructor tells the global WinApp object that it is being constructed.  If you examine the WinApp::WindowProc function, you will see how this is actually accomplished, and how the incoming Window messages are routed to the appropriate member function.  Much thought was given to that aspect of the functioning of DWinLib before settling upon this method.

I also stated earlier that it can be quite painful to tie your application's logic and data into the WndProc procedures of a pure API program.  Using the above wrapper, it is no longer difficult to tie your data into a window: simply put it in the class definition of the window derived from WinBaseO and use it as you would use data in any C++ class.  The application logic also flows much easier in a program written using a wrapper, if you are conversant in object oriented techniques.

If you want to see the above method in action, you can either cut-and-paste the previous code into a blank project, or you can examine the above zip file, and see how a Tic-Tac-Toe program can be put together modularly with this wrapper.  The Tic-Tac-Toe code is significantly flushed out from the above code, in that many more messages are handled, exception handling has been included, and it is not as trivial as the above code.  The Tic-Tac-Toe program also wraps a couple of Windows Common Controls, and does so in one of the cleanest examples I have ever seen.  (I'm tooting my own horn here, as it was kinda painful extracting the information and getting it to work from the examples I could find.  Microsoft does not make great examples for some of the common controls, and I did not find much better in my limited search at the time I constructed that application back in about '02.)

Even though this is the framework that DWinLib is based upon at its core, DWinLib is far advanced from the preceding code.  There will be more on the internals of DWinLib, and what it does in addition to the above, in another article.

The End

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

RandomMonkey


Just another hardworking guy who also went by the name of David O'Neil once.
Occupation: Web Developer
Location: United States United States

Other popular Libraries articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 10 of 10 (Total in Forum: 10) (Refresh)FirstPrevNext
GeneralWrapping Windows Common ControlsmemberStone Free0:49 26 Sep '06  
GeneralRe: Wrapping Windows Common ControlsmemberDavid O'Neil5:51 26 Sep '06  
GeneralNote about GWL_USERDATAsitebuilderMichael Dunn3:26 21 Feb '06  
GeneralRe: Note about GWL_USERDATAmemberRandomMonkey7:17 21 Feb '06  
GeneralRe: Note about GWL_USERDATAmemberJörgen Sigvardsson11:46 21 Feb '06  
GeneralRe: Note about GWL_USERDATAmemberRandomMonkey19:04 21 Feb '06  
GeneralRe: Note about GWL_USERDATAmemberJörgen Sigvardsson21:54 21 Feb '06  
GeneralRe: Note about GWL_USERDATAmemberRandomMonkey3:03 22 Feb '06  
GeneralRe: Note about GWL_USERDATAsitebuilderMichael Dunn12:36 21 Feb '06  
GeneralRe: Note about GWL_USERDATAmemberRandomMonkey19:10 21 Feb '06  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 20 Feb 2006
Editor:
Copyright 2006 by RandomMonkey
Everything else Copyright © CodeProject, 1999-2008
Web18 | Advertise on the Code Project