The Tools.h++ Class Library can be linked as a Microsoft Windows 3.X Dynamic Link Library (DLL). In a DLL, linking occurs at run time when the routine is actually used. This results in much smaller executables because routines are pulled in only as needed. Another advantage is that many applications can share the same code, rather than duplicating it in each application.
Because Windows and C++ technology is evolving so rapidly, be sure to check the file TOOLDLL.DOC on your distribution disk for more updates.
This section discusses a sample Windows application, DEMO.CPP, that uses the Tools.h++ DLL. The code for this program can be found in the subdirectory DLLDEMO. This program falls into the familiar category of "Hello World" examples.
The program is somewhat unusual, however, in that it maintains a linked list of Drawable objects you can insert into the window at run time. You can find the list, which is implemented using class RWSlistCollectables, in the subdirectory DLLDEMO. The discussion in this section assumes that you are somewhat familiar with Windows 3.X programming, but not with its relationship to C++.
Here's the main program of DEMO.CPP, the sample Windows application that uses the Tools.h++ DLL.
/* * Sample Windows 3.X program, using the Tools.h++ DLL. */ #include "demowind.h" int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR , int nCmdShow) // 1 { // Create an instance of the "DemoWindow" class: DemoWindow ww(hInstance, hPrevInstance, nCmdShow); // 2 // Add some items to it: ww.insert( new RWRectangle(200, 50, 250, 100)); // 3 ww.insert( new RWEllipse(50, 50, 100, 100)); ww.insert( new RWText(20, 20, "Hello world, from Rogue Wave!")); ww.show(); // Show the window // 4 ww.update(); // Update the window // Enter the message loop: MSG msg; while( GetMessage( &msg, NULL, 0, 0)) // 5 { TranslateMessage( &msg ); DispatchMessage( &msg ); } return msg.wParam; // 6 }
Here's the line-by-line description of the program.
//1 | This is the Windows program entry point that every Windows program must have. It is equivalent to C's main function. |
//2 | This creates an instance of the class DemoWindow, which we will see later. It represents an abstract window into which objects can be inserted. |
//3 | Here we insert three different objects into the DemoWindow: a rectangle, an ellipse, and a text string. |
//4 | This tells the DemoWindow to show itself and to update itself. |
//5 | Finally, the windows main event loop is entered. |
//6 | The DemoWindow destructor frees all memory allocated for its window objects. |
This header file declares the class DemoWindow. A key feature is the singly linked list called myList, which holds the list of items that have been inserted into the window. The member function DemoWindow::insert(RWDrawable*) allows new items to be inserted. Only objects that inherit from class RWDrawable, to be defined in Section 18.1.1.4, may be inserted.
The member function paint() may be called when it is time to repaint the window. The list myList will be traversed and each item in it will be repainted onto the screen.
Here's a listing of the DEMOWIND.H file
#ifndef __DEMOWIND_H__ #define __DEMOWIND_H__ /* * A Demonstration Window class --- allows items that * inherit from the base class RWDrawable to be * "inserted" into it. */ #include <windows.h> #include <rw/slistcol.h> #include "shapes.h" class DemoWindow { HWND hWnd; // My window handle HINSTANCE myInstance; // My instance's handle int nCmdShow; RWSlistCollectables myList; // A list of items in the window public: DemoWindow(HINSTANCE mom, HINSTANCE prev, int); ~DemoWindow(); void insert(RWDrawable*); // Add a new item to the window HWND handle() {return hWnd;} int registerClass(); // First time registration void paint(); // Called by Windows procedure int show() {return ShowWindow(hWnd,nCmdShow);} void update() {UpdateWindow(hWnd);} friend long FAR PASCAL _export DemoWindow_Callback(HWND, UINT, WPARAM, LPARAM); }; DemoWindow* RWGetWindowPtr(HWND h); void RWSetWindowPtr(HWND h, DemoWindow* p); #endif
Now let's look at the definitions of the public functions of class DemoWindow: the DemoWindow constructor, the DemoWindow destructor, and the member functions insert(), registerClass(), and paint(). Detailed comments follow this listing.
#include <windows.h> #include <rw/vstream.h> #include "demowind.h" #include "shapes.h" #include <stdlib.h> #include <string.h> /* * Construct a new window. */ DemoWindow::DemoWindow( HINSTANCE mom, HINSTANCE prev, int cmdShow) //1 { myInstance = mom; nCmdShow = cmdShow; // Register the class if there was no previous instance: if(!prev) registerClass(); //2 hWnd = CreateWindow("DemoWindow", //3 "DemoWindow", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, myInstance, (LPSTR)this ); // Stash away 'this' //4 if(!hWnd) exit( FALSE ); } /* * Register self. Called once only. */ int DemoWindow::registerClass(){ //5 WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = ::DemoWindow_Callback; //6 wndclass.cbClsExtra = 0; // Request extra space to store the 'this' pointer wndclass.cbWndExtra = sizeof(this); //7 wndclass.hInstance = myInstance; wndclass.hIcon = 0; wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); wndclass.hbrBackground = GetStockObject( WHITE_BRUSH ); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "DemoWindow"; if( !RegisterClass(&wndclass) ) exit(FALSE); return TRUE; } DemoWindow::~DemoWindow(){ // Delete all items in my list: myList.clearAndDestroy(); //8 } void DemoWindow::insert(RWDrawable* d){ // Add a new item to the window: myList.insert(d); //9 } void DemoWindow::paint(){ //10 RWDrawable* shape; PAINTSTRUCT ps; BeginPaint( handle(), &ps); //11 // Draw all items in the list. Start by making an iterator: RWSlistCollectablesIterator next(myList); //12 // Now iterate through the collection, drawing each item: while( shape = (RWDrawable*)next() ) //13 shape->drawWith(ps.hdc); //14 EndPaint( handle(), &ps ); //15 } /* * The callback routine for this window class. */ long FAR PASCAL _export DemoWindow_Callback(HWND hWnd, unsigned iMessage, //16 WPARAM wParam, LPARAM lParam) { DemoWindow* pDemoWindow; if( iMessage==WM_CREATE ){ //17 // Get the "this" pointer out of the create structure // and put it in the windows instance: LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam; pDemoWindow = (DemoWindow*) lpcs->lpCreateParams; RWSetWindowPtr(hWnd, pDemoWindow); return NULL; } // Get the appropriate "this" pointer pDemoWindow = RWGetWindowPtr(hWnd); //18 switch( iMessage ){ case WM_PAINT: pDemoWindow->paint(); //19 break; case WM_DESTROY: PostQuitMessage( 0 ); break; default: return DefWindowProc(hWnd, iMessage, wParam, lParam); }; return NULL; } void RWSetWindowPtr(HWND h, DemoWindow* p){ //20 SetWindowLong(h, 0, (LONG)p); } DemoWindow* RWGetWindowPtr(HWND h){ //21 return (DemoWindow*)GetWindowLong(h, 0); }
//1 | This is the constructor for DemoWindow. It requires the handle of the application instance creating it, mom, the handle of any previously existing instance, prev, and whether to show the window in iconic form. These variables are as received from WinMain, the main windows procedure that we have already seen. |
//2 | The constructor checks to see if any previous application instance has been run and, if not, registers the class. |
//3 | The new window is created. |
//4 | A key feature is the use of the Windows extra data feature to store the this pointer for this DemoWindow. The procedure to do this is somewhat cumbersome, but very useful. The value placed here will appear in the CREATESTRUCT structure, which we can retrieve when processing the WM_CREATE message generated by the CreateWindow function. |
//5 | This member function is only called for the first instance of an application. |
//6 | The global function DemoWindow_Callback is registered as the callback procedure for this window. |
//7 | We ask Windows to set aside space for the this pointer in this Windows class. This will be used to associate a Windows handle with a particular DemoWindow. |
//8 | The destructor calls clearAndDestroy() to delete all items that have been inserted into myList. |
//9 | The member function insert(RWDrawable*) inserts a new item into the window. Because RWDrawable inherits from RWCollectable, as we shall see in Section 18.1.1.4, there is no need to do a cast when calling RWSlistCollectables::insert(RWCollectable*). By making myList private and offering a restricted insert that takes arguments of type RWDrawable* only, we ensure that only drawables will be inserted into the window. |
//10 | Here's the paint procedure, called when it is time to repaint the window. |
//11 | We start by getting the handle for this window, then calling BeginPaint to fill a PAINTSTRUCT with information about the painting. |
//12 | An iterator is constructed in preparation for traversing the list of items to be painted. |
//13 | Get the next item to be painted. If nil is returned, then there are no more items and the while loop will finish. |
//14 | For each item, call the virtual function drawWith, causing the item to paint itself onto the given device context. |
//15 | The PAINTSTRUCT is returned. |
//16 | Here's the callback routine to be called when it is necessary to process a message for this window. It uses the very convenient _export keyword of Borland C++ to indicate that this function should be exported. If you use this procedure, you don't have to list the function in an Exports section of the module definition file. |
//17 | If the message is a WM_CREATE message, it was generated by the CreateWindow function and this is the first time through for this procedure. Use the rather baroque procedure to fetch the this pointer from the CREATESTRUCT (recall it had been inserted at line 4), and put it into the Windows extra data. The function RWSetWindowPtr will be defined later. |
//18 | The function RWGetWindowPtr(HWND) is used to retrieve the pointer to the appropriate DemoWindow, given a Windows HANDLE. |
//19 | If a WM_PAINT has been retrieved, then call the paint() member function, which we have already seen. |
//20 | This function is used to put a this pointer into the Windows extra data. The idea is to have a one-to-one mapping of Windows handles to instances of DemoWindow. |
//21 | This function is used to fetch the this pointer back out. |
This section deals with the subclasses of RWDrawable. Class RWDrawable is an abstract base class that inherits from the Tools.h++ class RWCollectable, and adds a new member function drawWith(HDC). Subclasses specializing RWDrawable should implement this function to draw itself onto the supplied device context handle.
We have reprinted only one subclass here, RWRectangle. Class RWRectangle inherits from RWDrawable. Note that it uses the struct RECT provided by Windows as member data to hold the corners of the rectangle.
class RWDrawable : public RWCollectable{ public: virtual void drawWith(HDC) const = 0; }; class RWRectangle : public RWDrawable{ RWDECLARE_COLLECTABLE(RWRectangle) public: RWRectangle() { } RWRectangle(int, int, int, int); // Inherited from RWDrawable: virtual void drawWith(HDC) const; // Inherited from RWCollectable: virtual Rwspace binaryStoreSize() const {return 4*sizeof(int);} virtual unsigned hash() const; virtual RWBoolean isEqual(const RWCollectable*) const; virtual void restoreGuts(RWvistream& s); virtual void restoreGuts(RWFile&); virtual void saveGuts(RWvostream& s) const; virtual void saveGuts(RWFile&) const; private: RECT bounds; // The bounds of the rectangle };
For the purposes of this DLL demo, it really isn't necessary to provide definitions for any of the member functions inherited from RWCollectable, but let's do it anyway, for the sake of completeness.
#include "shapes.h" #include <rw/vstream.h> #include <rw/rwfile.h> RWDEFINE_COLLECTABLE(RWRectangle, 0x1000) //1 // Constructor RWRectangle::RWRectangle(int l, int t, int r, int b) //2 { bounds.left = l; bounds.top = t; bounds.right = r; bounds.bottom = b; } // Inherited from Drawable: void RWRectangle::drawWith(HDC hdc) const //3 { // Make the Windows call: Rectangle(hdc, bounds.left, bounds.top, bounds.right, bounds.bottom); } // Inherited from RWCollectable: unsigned RWRectangle::hash() const //4 { return bounds.left ^ bounds.top ^ bounds.bottom ^ bounds.right; } RWBoolean RWRectangle::isEqual(const RWCollectable* c) const //5 { if(c->isA() != isA() ) return FALSE; const RWRectangle* r = (const RWRectangle*)c; return bounds.left == r->bounds.left && bounds.top == r->bounds.top && bounds.right == r->bounds.right && bounds.bottom == r->bounds.bottom; } // Restore the RWRectangle from a virtual stream: void RWRectangle::restoreGuts(RWvistream& s) //6 { s >> bounds.left >> bounds.top; s >> bounds.right >> bounds.bottom; } // Restore from an RWFile: void RWRectangle::restoreGuts(RWFile& f) //7 { f.Read(bounds.left); f.Read(bounds.top); f.Read(bounds.right); f.Read(bounds.bottom); } void RWRectangle::saveGuts(RWvostream& s) const //8 { s << bounds.left << bounds.top; s << bounds.right << bounds.bottom; } void RWRectangle::saveGuts(RWFile& f) const //9 { f.Write(bounds.left); f.Write(bounds.top); f.Write(bounds.right); f.Write(bounds.bottom); }
//1 | This is a macro that all subclasses of RWCollectable are required to compile once, and only once. See Section 15.2 for more details. |
//2 | This is the constructor for RWRectangle that fills in the RECT structure. |
//3 | This is the definition of the virtual function drawWith(HDC). It simply makes a call to the Windows function Rectangle() with appropriate arguments. |
//4 | Supplies an appropriate hashing value in case we ever want to retrieve this RWRectangle from a hash table. |
//5 | Supplies an appropriate isEqual() implementation. |
//6 | This function retrieves the RWRectangle from a virtual stream. |
//7 | This function retrieves the RWRectangle from an RWFile. |
//8 | This function stores the RWRectangle on a virtual stream. Note how there is no need to separate elements by white space_this will be done by the virtual stream, if necessary. |
//9 | This function stores the RWRectangle on an RWFile. |
The other shapes, RWEllipse and RWText, are implemented in a similar manner.