|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
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
IntroductionThis 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 WrappersThere 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 DifferencesFor 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 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 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 WindowThe 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 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 When a In this example, only a 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 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 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 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
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||