Rogue Wave banner
Previous fileTop of documentContentsIndexNext file

18.1 Dynamic Link Library

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.

18.1.1 The DLL Example

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.

Figure 1. The demo program window.

image19.gif

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++.

18.1.1.1 The DEMO.CPP Code

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.

//1This is the Windows program entry point that every Windows program must have. It is equivalent to C's main function.
//2This creates an instance of the class DemoWindow, which we will see later. It represents an abstract window into which objects can be inserted.
//3Here we insert three different objects into the DemoWindow: a rectangle, an ellipse, and a text string.
//4This tells the DemoWindow to show itself and to update itself.
//5Finally, the windows main event loop is entered.
//6The DemoWindow destructor frees all memory allocated for its window objects.

18.1.1.2 DEMOWIND.H

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

18.1.1.3 The DEMOWIND.CPP File

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);
}
//1This 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.
//2The constructor checks to see if any previous application instance has been run and, if not, registers the class.
//3The new window is created.
//4A 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.
//5This member function is only called for the first instance of an application.
//6The global function DemoWindow_Callback is registered as the callback procedure for this window.
//7We 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.
//8The destructor calls clearAndDestroy() to delete all items that have been inserted into myList.
//9The 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.
//10Here's the paint procedure, called when it is time to repaint the window.
//11We start by getting the handle for this window, then calling BeginPaint to fill a PAINTSTRUCT with information about the painting.
//12An iterator is constructed in preparation for traversing the list of items to be painted.
//13Get the next item to be painted. If nil is returned, then there are no more items and the while loop will finish.
//14For each item, call the virtual function drawWith, causing the item to paint itself onto the given device context.
//15The PAINTSTRUCT is returned.
//16Here'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.
//17If 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.
//18The function RWGetWindowPtr(HWND) is used to retrieve the pointer to the appropriate DemoWindow, given a Windows HANDLE.
//19If a WM_PAINT has been retrieved, then call the paint() member function, which we have already seen.
//20This 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.
//21This function is used to fetch the this pointer back out.

18.1.1.4 An Excerpt from SHAPES.H

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
};

18.1.1.5 An Excerpt from SHAPES.CPP

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);
}
//1This is a macro that all subclasses of RWCollectable are required to compile once, and only once. See Section 15.2 for more details.
//2This is the constructor for RWRectangle that fills in the RECT structure.
//3This is the definition of the virtual function drawWith(HDC). It simply makes a call to the Windows function Rectangle() with appropriate arguments.
//4Supplies an appropriate hashing value in case we ever want to retrieve this RWRectangle from a hash table.
//5Supplies an appropriate isEqual() implementation.
//6This function retrieves the RWRectangle from a virtual stream.
//7This function retrieves the RWRectangle from an RWFile.
//8This 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.
//9This function stores the RWRectangle on an RWFile.

The other shapes, RWEllipse and RWText, are implemented in a similar manner.


Previous fileTop of documentContentsIndexNext file
©Copyright 1999, Rogue Wave Software, Inc.
Send mail to report errors or comment on the documentation.