株式会社エス・スリー・フォー

18.1 ダイナミックリンクライブラリ

Tools.h++ クラスライブラリは、Microsoft Windows 3.X のダイナミックリンクライブラリ (DLL) としてリンクできます。DLLでは、ルーチンが実際に使用される実行時にリンクが行われます。この結果、ルーチンが必要に応じて引き出されるので、実行可能ファイルのサイズがずっと小さくなります。もう 1つの利点は、各アプリケーションごとに重複したコードを書くのではなく、多くのアプリケーションで同じコードを共有できることです。

Windows および C++ テクノロジーは急速に進化しているため、最新情報については、配布ディスクにあるファイル TOOLDLL.DOC を調べてください。

18.1.1 DLL の例

この節では、Tools.h++ の DLL を用いた Windows アプリケーションである DEMO.CPP について説明します。このプログラムのコードは、サブディレクトリ DLLDEMO にあります。このプログラムは、「Hello World」の例のような見慣れた部類のものでしょう。

図1.デモプログラムウィンドウ

image19

ただし、実行時にウィンドウに挿入できる Drawable オブジェクトのリンクリストを保持するという点において、このプログラムは多少珍しいものとなっています。RWSlistCollectables クラスを用いて実装されているリストは、サブディレクトリ DLLDEMO にあります。この節では、読者に Windows 3.X プログラミングについての知識はあるが、C++ との関係についてはよく知らないものとして説明します。

18.1.1.1 DEMO.CPP コード

次に、Tools.h++ の DLL を用いた Windows アプリケーションである DEMO.CPP のメインプログラムを示します。

/*
 * Tools.h++ DLL を使ったサンプル Windows 3.X プログラム
 */

#include "demowind.h"

int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR , int nCmdShow)                                // 1
{  // "DemoWindow" クラスのインスタンスを作成する:
   DemoWindow ww(hInstance, hPrevInstance, nCmdShow);        // 2

   // それに項目をいくつか追加する:
   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();      // ウィンドウを表示する                        // 4
   ww.update();    // ウィンドウを更新する

   // メッセージループを入力する:
   MSG msg;
   while( GetMessage( &msg, NULL, 0, 0))                     // 5
   {
     TranslateMessage( &msg );
     DispatchMessage( &msg );
   }

   return msg.wParam;                                        // 6
}

次に、コードを行ごとに説明します。

  • //1 これは、すべての Windows プログラムが持たなければならない Windows プログラムの開始部分です。これは、C の main 関数と同等です。
  • //2 これは、 DemoWindow クラスのインスタンスを作成します。これについては後で検討します。これは、オブジェクトを挿入できる抽象ウィンドウを表わします。
  • //3 ここで、DemoWindow に長方形、楕円、テキスト文字列の 3 つの異なるオブジェクトを挿入します。
  • //4 これは、デモウィンドウ自体を表示して、更新するように、 DemoWindow に指示します。
  • //5 最後に、ウィンドウのメインイベントのループを入力します。
  • //6 DemoWindow デストラクタがウィンドウオブジェクトに割り当てられていたメモリを解放します。

18.1.1.2 DEMOWIND.H

このヘッダファイルで、DemoWindow クラスが定義されます。主な機能は、myList と呼ばれる一重リンクリストで、ウィンドウに挿入された項目のリストを管理します。DemoWindow::insert(RWDrawable*) メンバ関数により、新しい項目を挿入することができます。18.1.1.4 節で説明するように、RWDrawable クラスを継承するオブジェクトだけを挿入できます。

ウィンドウを再描画するときには、paint() メンバ関数を呼び出すことができます。リスト myList を走査して、そのリストの各項目を画面に再描画します。

次に、DEMOWIND.H を示します。

#ifndef __DEMOWIND_H__
#define __DEMOWIND_H__

/*
 * デモウィンドウクラスにより、基底クラス RWDrawable を
 * 継承する項目をデモウィンドウに
 * 挿入できる
 */

#include <windows.h>
#include <rw/slistcol.h>
#include "shapes.h"

class DemoWindow {
  HWND                  hWnd;                 // ウィンドウハンドル
  HINSTANCE             myInstance;       // インスタンスのハンドル
  int                   nCmdShow;
  RWSlistCollectables   myList;  // ウィンドウにある項目のリスト

public:

  DemoWindow(HINSTANCE mom, HINSTANCE prev, int);
  ~DemoWindow();

    void   insert(RWDrawable*);   // 新しい項目をウィンドウに追加する

  HWND     handle() {return hWnd;}
  int      registerClass();            // 最初の登録
  void     paint();                // Windows プロシージャによって呼び出される
  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 DEMOWIND.CPP ファイル

ここで、DemoWindow クラスの公開関数である DemoWindow コンストラクタ、DemoWindow デストラクタ、および insert()registerClass()paint()の各メンバ関数の定義を見てみましょう。詳しい説明は、コードリストの後にあります。

#include <windows.h>
#include <rw/vstream.h>
#include "demowind.h"
#include "shapes.h"
#include <stdlib.h>
#include <string.h>

/*
 * 新しいウィンドウを生成する
 */
DemoWindow::DemoWindow( HINSTANCE mom, HINSTANCE prev,
                        int cmdShow)                          //1
{
  myInstance = mom;
  nCmdShow = cmdShow;

  // 前のインスタンスがなかった場合は、クラスを登録する:
  if(!prev) registerClass();                                  //2

  hWnd = CreateWindow("DemoWindow",                           //3
                      "DemoWindow",
                      WS_OVERLAPPEDWINDOW,
                      CW_USEDEFAULT, 0,
                      CW_USEDEFAULT, 0,
                      NULL,
                      NULL,
                      myInstance,
                     (LPSTR)this );    // 'this' を隠す   //4

  if(!hWnd) exit( FALSE );
}

/*
 * 自分自身を登録する。1 度だけ呼ばれる
 */
int
DemoWindow::registerClass(){                                 //5
   WNDCLASS wndclass;
   wndclass.style = CS_HREDRAW | CS_VREDRAW;
   wndclass.lpfnWndProc = ::DemoWindow_Callback;             //6
   wndclass.cbClsExtra = 0;
   // 'this' ポインタを格納するための領域を要求する。
   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(){
   // リストからすべての項目を削除する:
   myList.clearAndDestroy();                                 //8
}

void
DemoWindow::insert(RWDrawable* d){
   // 新しい項目をウィンドウに追加する:
   myList.insert(d);                                         //9
}

void
DemoWindow::paint(){                                        //10
   RWDrawable* shape;
   PAINTSTRUCT ps;
   BeginPaint( handle(), &ps);                              //11

   // リストにあるすべての項目を描画する。反復子を生成して開始する:
   RWSlistCollectablesIterator next(myList);                //12

   // 各項目を描画しながらコレクション全体を反復する:
   while( shape = (RWDrawable*)next() )                     //13
     shape->drawWith(ps.hdc);                               //14

   EndPaint( handle(), &ps );                               //15
}

/*
 * このウィンドウクラスのコールバックルーチン
 */
long FAR PASCAL _export
DemoWindow_Callback(HWND hWnd, unsigned iMessage,           //16
                    WPARAM wParam, LPARAM lParam)
{
   DemoWindow* pDemoWindow;

   if( iMessage==WM_CREATE ){                               //17
     // "this" ポインタを作成構造体から取得して
     // それをウィンドウインスタンスに挿入する:
     LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
     pDemoWindow = (DemoWindow*) lpcs->lpCreateParams;
     RWSetWindowPtr(hWnd, pDemoWindow);
     return NULL;
  }

   // 適切な "this" ポインタを取得する
   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 これは、DemoWindow のコンストラクタです。このコンストラクタは、これを作成するアプリケーションインスタンスのハンドルである mom、既存のインスタンスのハンドルである prev、そしてウィンドウをアイコン表示するかどうかを要求します。これらの変数は、これまでに見たメインウィンドウプロシージャである WinMain から受け取ります。
  • //2 コンストラクタが、以前のアプリケーションインスタンスが実行されているかどうかを調べ、実行されていない場合はクラスを登録します。
  • //3 新しいウィンドウが作成されます。
  • //4 主な機能は、現在の DemoWindow の this ポインタを格納する Windows のエキストラデータ機能の使用です。これを行うプロシージャは、やや大きくなりますが、非常に便利です。ここに入る値は、CREATESTRUCT 構造体に現われ、そこから CreateWindow 関数によって生成される WM_CREATE メッセージを取り出すことができます。
  • //5 このメンバ関数は、アプリケーションの最初のインスタンスでだけ呼び出されます。
  • //6 DemoWindow_Callback グローバル関数は、このウィンドウのコールバックプロシージャとして登録されています。
  • //7 この Windows クラスの this ポインタのために領域を確保するように、Windows に要求します。これは、Windows ハンドルを特定の DemoWindow に関連付けるために使用されます。
  • //8 このデストラクタは、clearAndDestroy() を呼び出し、myList にこれまで挿入された項目すべてを削除します。
  • //9 insert(RWDrawable*) メンバ関数がウィンドウに項目を挿入します。18.1.1.4節で説明があるように、RDrawableRWCollectable を継承するので、RWSlistCollectables::insert(RWCollectable*) を呼び出すときにキャストを行う必要はありません。myList を非公開にして、RWDrawable* 型の引数だけをとるように挿入に制約を加えることにより、確実に描画できるものだけがウィンドウに挿入されます。
  • //10 これは、ウィンドウを再描画するとき呼び出される描画プロシージャです。
  • //11 このウィンドウのハンドルを取得することから始めて、BeginPaint を呼び出し、PAINTSTRUCT に描画についての情報を与えます。
  • //12 描画する項目のリストを走査する準備過程で、反復子が生成されます。
  • //13 次に描画する項目を取得します。nil が返された場合は、描画する項目がもうないことを示すので、while ループが終了します。
  • //14 各項目ごとに、drawWith 仮想関数を呼び出し、指定されたデバイスに項目を描画します。
  • //15 PAINTSTRUCT が返されます。
  • //16 これは、このウィンドウ向けのメッセージを処理する必要があるときに呼び出されるコールバックルーチンです。非常に便利な Borland C++ のキーワードである _export を使って、この関数が書き出されなければならないことを示します。このプロシージャを使う場合は、モジュールの定義ファイルの Export セクションに関数をリストする必要はありません。
  • //17 メッセージが WM_CREATE メッセージの場合は、CreateWindow 関数によってメッセージが生成されます。このプロシージャはここで初めて使用されます。このやや変ったプロシージャを使って CREATESTRUCT から this ポインタ (// 4 ですでに挿入されている) を取って、それを Windows エキストラデータに入れます。RWSetWindowPtr 関数はあとで定義します。
  • //18 RWGetWindowPtr(HWND) 関数を使って、Windows の HANDLE を与え、ポインタを適切な DemoWindow に読み出します。
  • //19 WM_PAINT がすでに読み出されている場合は、paint() 関数が読み出されます。これについては、すでに検討しました。
  • //20 this ポインタを Windows エキストラデータに入れるためにこの関数を使います。この目的は、Windows ハンドルを DemoWindow のインスタンスに一対一でマップすることにあります。
  • //21 this ポインタを取り、返すためにこの関数を使います。

18.1.1.4 SHAPES.H の一部

この節では、RWDrawable のサブクラスについて取り上げます。RWDrawable クラスは、Tools.h++RWCollectable クラスから派生した抽象基底クラスで、drawWith(HDC) メンバ関数を持ちます。RWDrawable を特殊化するサブクラスには、この関数を実装してください。この関数は、供給デバイスに依存するハンドルにそれ自体を描画します。

ここでは、ただ 1 つのサブクラス、RWRectangle だけを再描画しました。RWRectangle クラスは、RWDrawable を継承しています。この関数は、長方形の角を維持するメンバデータとして Windows によって与えられた構造体 RECT を使用する点に注意してください。

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

  // RWDrawable を継承:
  virtual void  drawWith(HDC) const;

  // 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;                    // 長方形の境界
};

18.1.1.5 SHAPES.CPP の一部

RWCollectable を継承したメンバ関数の定義はこの DLL デモの目的からは外れますが、完璧性を期すために以下にそのコードを示します。

#include "shapes.h"
#include <rw/vstream.h>
#include <rw/rwfile.h>

RWDEFINE_COLLECTABLE(RWRectangle, 0x1000)                    //1

// コンストラクタ
RWRectangle::RWRectangle(int l, int t, int r, int b)         //2
{
   bounds.left   = l;
   bounds.top    = t;
   bounds.right  = r;
   bounds.bottom = b;
}

// Drawable を継承:
void
RWRectangle::drawWith(HDC hdc) const                         //3
{
   // Windows の呼び出しを行う:
   Rectangle(hdc, bounds.left, bounds.top,
             bounds.right, bounds.bottom);
}

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

// 仮想ストリームから RWRectangle を復元する:
void
RWRectangle::restoreGuts(RWvistream& s)                      //6
{
   s >> bounds.left  >> bounds.top;
   s >> bounds.right >> bounds.bottom;
}

// 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 このマクロを使ってただ一度だけコンパイルするには、RWCollectable のサブクラスがすべて必要です。詳しくは、15.2 節を参照してください。
  • //2 これは、RECT 構造体を満たす RWRectangle のコンストラクタです。
  • //3 これは、drawWith(HDC) 仮想関数の定義です。これは、単に適切な引数を持つ Windows 関数の Rectangle() を呼び出します。
  • //4 ハッシュ表からこの RWRectangle を取り出す場合に、適切なハッシュ値を与えます。
  • //5 isEqual() を適切に実装します。
  • //6 この関数は、仮想ストリームから RWRectangle を取り出します。
  • //7 この関数は、RWFile から RWRectangle を取り出します。
  • //8 この関数は、仮想ストリームに RWRectangle を格納します。各項目を空白類で区切る必要ないことに注意してください。これは、仮想ストリームが必要に応じて行います。
  • //9 この関数は、RWFileRWRectangle を書き出します。

ほかの形については、同様に RWEllipseRWText を実装します。