15章 補講(その2)

<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis"></meta>
<title>15章 補講(その2)</title>
</head>

<body bgcolor="WHITE">
<p>
<font size="5">15章 補講(その2)</font>
<hr>
<p>
 今回も補講です。基本的に14章の続きです。つまり、<b>・WNDCLASS構造体のcbWndExtraメンバ変数の使い方</b>の続きです。そして、前回の予告通り、5章で作成したお絵描きプログラムをコントロール化(今回の場合、親ウインドウに通知メッセージを送らないから、単に子ウインドウか?)する過程で、WNDCLASS構造体のcbWndExtraメンバ変数の使い方を具体的に学んでいきましょう。
<p align="CENTER">
<img src="/web/20160504210551im_/http://web.kyoto-inet.or.jp:80/people/ysskondo/img15.gif"><br>
<a href="chap15.lzh">ダウンロード</a>
<hr>
<p>
<b>・5章の簡単な復習</b>
<p>
 5章で学んだ重要な概念の1つは、ユーザーインターフェースとデータ管理の分離でした。データ管理部は、モジュールとして独立した1つのファイルに隔離されました。そして、ユーザーインターフェース部とデータ管理部とのやり取りは、データ管理部が外部に公開しているインターフェース(関数の集合)を介して行いました。つまり、ユーザーインターフェース側は、データ管理部のインターフェースの仕様のみを意識してコーディングが出来、その実装を意識しなくて済みました。
<p>
<b>・今回のお絵描きプログラムの作成方針</b>
<p>
 まず、データ管理部を実装したDLL(C15Dlla.Dll)を作成します。次に、そのDLLを用いて、ユーザから受け取ったデータを画面に描画し管理する子ウインドウを実装したDLL(C15Dllb.Dll)を作成します。そして、C15Dllb.Dllを用いてアプリケーションとするプログラム(C15Main.exe)を作成します。
<p>
 C15DLLb.DLLはC15Dlla.Dllを暗黙的にリンクし、C15Main.exeはC15Dllb.Dllを明示的にリンクします。
<hr>
<p>
<b>・データ管理部を実装しているDLL(C15Dlla.Dll)を見てみよう</b>
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト15-1(Doc.h)
<tr><td>
<pre>
/*
  『C言語で始めるWindowsプログラミング』
   15章 補講(その2)のサンプルプログラム
                   Programmed by Y.Kondo

/


#ifdef _KONDO_MAKE_DLL_

# define __PORT __declspec(dllexport) /* DLLを作る場合 */
#else
# define __PORT __declspec(dllimport) /* DLLを使う場合 */
#endif
<b>
/*
  ドキュメントを作成する関数
   引数    :無し
   戻り値  :正常時=インスタンスハンドル
           :異常時=(-1)

/

__PORT int CreateDoc(void);
</b>
/*
  ドキュメントの最後にデータを付け加える関数
   引数    :左から順に
           :CreateDocで取得したインスタンスハンドル
           :型(ユーザー定義)
           :サイズ(ユーザー定義)
           :データへのアドレス
   戻り値  :正常時=1
           :異状時=0

/

__PORT int AddLast(int,int,int,void*);

/*
  カーソルが最初のデータを示すようにする関数
   引数    :CreateDocで取得したインスタンスハンドル
   戻り値  :カーソルが最初のデータを示せた場合=1
           :カーソルが最初のデータを示せなかった場合=0
   注意    :この関数によってカーソルの位置は変化しない

/

__PORT int First(int);

/*
  カーソルが次のデータを示すようにする関数
   引数    :CreateDocで取得したインスタンスハンドル
   戻り値  :カーソルが次のデータを示せた場合=1
           :カーソルが次のデータを示せなかった場合=0

/

__PORT int Next(int);

/*
  カーソルの位置のデータを削除する関数
   引数    :CreateDocで取得したインスタンスハンドル
   戻り値  :削除できた場合        =1
           :削除できなかった場合  =0
   注意    :削除後、削除したデータの次のデータをカーソルは示す

/

__PORT int Delete(int);

/*
  ドキュメントを破棄する関数
   引数    :CreateDocで取得したインスタンスハンドル
   戻り値  :正常時=1
           :異状時=0

/

__PORT int DestroyDoc(int);

/*
  カーソルの位置のデータの型を取得する関数
   引数    :CreateDocで取得したインスタンスハンドル
   戻り値  :正常時=ユーザーがAddLast関数で入力した値
           :異常時=0
   注意    :異状時、カーソルが正しくデータを示していない

/

__PORT int GetDataType(int);

/*
  カーソルの位置のデータのサイズを取得する関数
   引数    :CreateDocで取得したインスタンスハンドル
   戻り値  :正常時=ユーザーがAddLast関数で入力した値
           :異常時=0
   注意    :異状時、カーソルが正しくデータを示していない

/

__PORT int GetDataSize(int);

/*
  カーソル位置のデータへのアドレスを返す関数
   引数    :CreateDocで取得したインスタンスハンドル
   戻り値  :正常時=カーソル位置のデータへのアドレス
           :異常時=NULL
   注意    :戻り値はキャストする必要がある

/

__PORT void const *GetData(int);
</pre>
</table>
<p>
 5章と同様に、ヘッダファイルは、モジュールのインターフェースの仕様書と等価です。5章に比べて目新しいのは、<b><i><u>CreateDoc関数</u></i></b>が追加されている事。これと、<b><i><u>この関数以外の全ての関数は第一引数として、CreateDoc関数の戻り値、つまり、インスタンスハンドルを受け取る事です</u></i></b>。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト15-2(Doc.c)
<tr><td>
<pre>

/*
  『C言語で始めるWindowsプログラミング』
   15章 補講(その2)のサンプルプログラム
                   Programmed by Y.Kondo

/


/*
  このDLLは、5章のサンプルプログラムを元に作成しました    
   このサンプルプログラムから、“ハンドル”とは何かが分かるのでは
   ないでしょうか?ハンドルの実装の例です

/


#define STRICT
#define _KONDO_MAKE_DLL_ /* Doc.hに作用する */

#define MAX_INSTANCE 100 /* インスタンスの最大数 */

#include &lt;windows.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include "doc.h"

/*
  データを双方向リストで繋ぐためのノード(節)型の定義

/

typedef struct tagT {
  int     DataType;       /*  データタイプ:利用者定義    */
   int     DataSize;       /*  データサイズ:利用者定義    */
   void*   Data;           /*  データ本体へのポインタ      */
   struct tagT *Previous;  /*  リスト構造の為のポインタ    */
   struct tagT *Next;      /*          同上                */
} TNode;

/*
  ファイルグローバルな変数定義

/

static TNode Head[MAX_INSTANCE]; /* 双方向リンクドリストの核 */
static TNode *Cursor[MAX_INSTANCE]; /* カーソル */
static int use_handle[<b>MAX_INSTANCE+1</b>]; /* インスタンスハンドルが使われておれば1 */
                                          /*  使われていなければ0                    */      

/*
  DLLエントリーポイント

/

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
  int cnt;    

  switch(fdwReason)
   {
       case    DLL_PROCESS_ATTACH:
           <b>for(cnt=0;cnt&lt;=MAX_INSTANCE;cnt++)</b>
               use_handle[cnt]=0;      /*  インスタンスハンドルが全て未使用に明示的に初期化    */
           break;
   }
   return  TRUE;
}

/*
  ドキュメントのインスタンスを作成する関数
   正常終了時は、インスタンスハンドルを返す
   異常終了時は、-1を返す
   本当は、異状終了時は0を返すようにした方が
   良いが、実装を単純にするため

/

__PORT int CreateDoc(void)
{
  int     handle; 

  /*  使えるインスタンスハンドルの値を探す    */
<b> for(handle=0; ;handle++)
      if(use_handle[handle]==0)
           break;

  if(handle==MAX_INSTANCE)
       return  -1;</b> /*  番兵なら、インスタンスハンドルを全部使い切っていると判断    */

  /*  対応するインスタンスハンドル値の核を初期化  */
   Head[handle].DataType   =0;
   Head[handle].DataSize   =0;
   Head[handle].Data       =NULL;
   Head[handle].Previous   =&Head;[handle];
   Head[handle].Next       =&Head;[handle];

  /*  対応するインスタンスハンドル値のカーソルを初期化    */
   Cursor[handle]=&Head;[handle];

  /*  ハンドルを使用済みにする    */
   use_handle[handle]=1;

  return  handle;
}

/*
  ドキュメントの最後にデータを付け加える関数
   正常終了時は、1を返し、異常終了時0を返す

/

__PORT int AddLast(int handle,int type,int size,void* data)
{
  TNode   *p;
   TNode   *newdata;
   p=Head[handle].Previous;

  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  0;                  /*  異常終了    */

  /*  ここでメモリ領域を取得  */
   if((newdata=malloc(sizeof(TNode)))==NULL)
       return  0;
   if((newdata-&gt;Data=malloc(size))==NULL)
   {
       free(newdata);
       return  0;
   }

  /*  ここで、新たなデータをセット    */
   newdata-&gt;DataType=type;
   newdata-&gt;DataSize=size;
   memcpy(newdata-&gt;Data,data,size);
   newdata-&gt;Previous    =Head[handle].Previous;     /*  リンク  */
   newdata-&gt;Next        =&Head;[handle];             /*  リンク  */

  /*  リンク  */
   p-&gt;Next                  =newdata;
   Head[handle].Previous   =newdata;

  return  1;
}

/*
  カーソルに最初のデータを示さす関数
   最初のデータをカーソルが示せた場合は1を返し
   示せなかった場合は、0を返す                

/

__PORT int First(int handle)
{
  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  0;                  /*  異常終了    */

  Cursor[handle]=Head[handle].Next;
   if(Cursor[handle]==&Head;[handle])
       return  0;
   return  1;
}

/*
  カーソルを次に1つ進める関数
   カーソルを動かせた場合は1を返す
   終端に達していた場合は、0を返す

/

__PORT int Next(int handle)
{
  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  0;                  /*  異常終了    */

  if(Cursor[handle]==&Head;[handle])
       return  0;
   Cursor[handle]=Cursor[handle]-&gt;Next;
   if(Cursor[handle]==&Head;[handle])
       return  0;
   return  1;
}

/*
  カーソル位置のデータを削除する関数
   成功すれば1を返し、失敗すれば0を
   返す。
   削除後、カーソルは削除したデータの
   次を示す

/

__PORT int Delete(int handle)
{
  TNode   *p;
   TNode   *n;

  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  0;                  /*  異常終了    */

  if(Cursor[handle]==&Head;[handle])
       return  0;
   p=Cursor[handle]-&gt;Previous;
   n=Cursor[handle]-&gt;Next;
   free(Cursor[handle]-&gt;Data);
   free(Cursor[handle]);
   p-&gt;Next      =n;
   n-&gt;Previous  =p;
   Cursor[handle]=n;           /*  削除したデータの次のデータを示すようにする  */
   return  1;
}

/*
  ドキュメントを破棄する関数

/

__PORT int DestroyDoc(int handle)
{
  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  0;                  /*  異常終了    */

  /*  実際にデータを削除している  */
   while(First(handle))
       Delete(handle);

  use_handle[handle]=0;       /*  ハンドルを開放  */

  return  1;
}

/*
  カーソル位置のデータの型を取得する関数
   カーソルが正しくデータのあるノードを示していない
   場合、0を返す。

/

__PORT int GetDataType(int handle)
{
  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  0;                  /*  異常終了    */

  if(Cursor[handle]==&Head;[handle])
       return  0;
   return  Cursor[handle]-&gt;DataType;
}

/*
  カーソル位置のデータの型を取得する関数
   カーソルが正しくデータのあるノードを示していない
   場合、0を返す。

/

__PORT int GetDataSize(int handle)
{
  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  0;                  /*  異常終了    */

  if(Cursor[handle]==&Head;[handle])
       return  0;

  return  Cursor[handle]-&gt;DataSize;
}

/*
  カーソル位置のデータへのポインタを返す関数
   カーソルが正しくデータのあるノードを示していない
   場合、NULLを返す。

/

__PORT void const *GetData(int handle)
{
  /*  インスタンスハンドルが有効かどうか調べる    */
   if(use_handle[handle]==0)
       return  NULL;                   /*  異常終了    */

  if(Cursor[handle]==&Head;[handle])
       return  NULL;

  return  Cursor[handle]-&gt;Data;
}
</pre>
</table>
<p>
 さて、今まで、ハンドルと言うのはOSなどから与えられる物というイメージでしたね。今回、ウインドウクラスを複数のウインドウで共有するために、ハンドルを与える側に立ちました。結局、<b><i><u>ハンドルというのは、同じ関数(ロジック)を使いまわすための技法なんですね</u></i></b>。
<p>
 さて、筆者は、3章で「ハンドルとは配列の添え字のような物だ」と書きました。今回のハンドルの実装は、まさに配列の添え字です。これ以外は、基本的に5章の説明通りです。つまり、環状の双方向リンクドリストですね。そして、これがMAX_INSTANCE本あって、これをハンドルなる整数で指定して、関数(ロジック)を使いまわしているにすぎないのです。きっと、Windows3.1ぐらいの下等なOSなら、マンマこの様な事をしていたのではないでしょうか?
<p>
 見所は、もう一つ。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
__PORT int CreateDoc(void)
{
  int     handle; 

  /*  使えるインスタンスハンドルの値を探す    */
   for(handle=0; ;handle++)
       if(use_handle[handle]==0)
           break;

  if(handle==MAX_INSTANCE)
       return  -1; /*  番兵なら、インスタンスハンドルを全部使い切っていると判断    */
</pre>
</table>
<p>
 これは、<b><i><u>番兵</u></i></b>というテクニックで、時間的計算オーダーを変えてしまうほどの効果はありませんが、処理速度を向上さす手法です。考え方は、確実にループが終了する様にしておいて、ループが終了した後、これが範囲外の物、つまり、番兵か調べる事により、条件分岐の数を減らすものです。for文の中に終了条件が無いのに注目してください。そして、use_handle[]配列の大きさが、MAX_INSTANCEより1大きい事にも注目してください。
<p>
 余談ですが、カリカリにチューニングというと、BASICよりPASCAL、PASCALよりC、Cよりアセンブラと短絡的に走ってしまい、アセンブラでプログラミングする事をカリカリにチューニングする事の様に思っているプログラマがいます。しかし、これは間違いですね。まずは、(時間的)計算オーダーを小さくするアルゴリズムが無いか?流れ込んで来るデータの特徴を見つけ、これに最適なアルゴリズムは何か?を考える事が先決です。これは、比較的言語非依存です。そして、選択したアルゴリズムが最適と判断した場合、余分な条件判断式を省けないかを考え、これでも駄目なら、言語を変えるのが、ベターと思います。恐らく、アセンブラで書いたバブルソートとBASICで書いたシェルソートでは、余程入力データ(の質と量)に変な癖が無い限り、BASICで組んだシェルソートの方が速いでしょう。
<hr>
<p>
<b>・描画機能を持つ子ウインドウを実装しているDLL(C15Dllb.Dll)を見てみよう</b>
<p>
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト15-3(Main.c)
<tr><td>
<pre>
/*
  『C言語で始めるWindowsプログラミング』
   15章のサンプルプログラム
                   Programmed by Y.Kondo

/


#define STRICT
#include &lt;windows.h&gt;

#include "doc.h"
#include "c15dllb.h"

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static ATOM RegSubWindowClass(HINSTANCE);
static LRESULT CALLBACK SubWindowProc(HWND,UINT,WPARAM,LPARAM);

/* DLLエントリーポイント */
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
  switch(fdwReason)
   {
       case    DLL_PROCESS_ATTACH: /*  プロセスに接続されたとき    */
           if(RegSubWindowClass(hinstDLL))
               return  TRUE;   /*  ウインドウクラスの登録に成功した場合    */
           else
               return  FALSE;  /*  失敗した場合    */

       case    DLL_PROCESS_DETACH: /*  プロセスから切り離されたとき    */
           UnregisterClass(YK_LINE_CONTROL,hinstDLL);
           break;
   }
   return  TRUE;
}

/* 子ウインドウのウインドウクラスを登録する関数 */
static ATOM RegSubWindowClass(HINSTANCE hInstance)
{
  WNDCLASS        wc;             /*  ウインドウクラス登録用の構造体          */

  wc.style        =CS_GLOBALCLASS; 
   wc.lpfnWndProc  =SubWindowProc;
   wc.cbClsExtra   =0;
   wc.cbWndExtra   =4;             /*  4バイトを確保  */
   wc.hInstance    =hInstance;
   wc.hIcon        =LoadIcon(NULL,IDI_APPLICATION);
   wc.hCursor      =LoadCursor(NULL,IDC_ARROW);
   wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
   wc.lpszMenuName =NULL;
   wc.lpszClassName=YK_LINE_CONTROL;
   
   return  RegisterClass(&wc;);     /*  ウインドウクラス登録    */
}

/*===============================================================================*/

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_CreateProc(HWND,LPCREATESTRUCT);
static LRESULT Wm_DestroyProc(HWND);
static LRESULT Wm_LButtonDownProc(HWND,WPARAM,WORD,WORD);
static LRESULT Wm_LButtonUpProc(HWND,WPARAM,WORD,WORD);
static LRESULT Wm_MouseMoveProc(HWND,WPARAM,WORD,WORD);
static LRESULT Wm_PaintProc(HWND);

/* メインウインドウのウインドウプロシージャ */
LRESULT CALLBACK SubWindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {
       case    WM_CREATE:
           return  Wm_CreateProc(hwnd,(LPCREATESTRUCT)lparam);     
       case    WM_DESTROY:         /*  ウインドウの破壊後処理              */
           return  Wm_DestroyProc(hwnd);
       case    WM_LBUTTONDOWN:     /*  マウスの左ボタンが押された時        */
           return  Wm_LButtonDownProc(hwnd,wparam,LOWORD(lparam),HIWORD(lparam));
       case    WM_LBUTTONUP:       /*  マウスの左ボタンが放された時        */
           return  Wm_LButtonUpProc(hwnd,wparam,LOWORD(lparam),HIWORD(lparam));
       case    WM_MOUSEMOVE:       /*  マウスが動かされた時                */
           return  Wm_MouseMoveProc(hwnd,wparam,LOWORD(lparam),HIWORD(lparam));
       case    WM_PAINT:           /*  再描画の必要な時に生じるメッセージ  */
           return  Wm_PaintProc(hwnd);
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

/*===============================================================================*/

/* このファイル内でのみ用いられる定数の定義 */
#define TYPE_MOVETO 1
#define TYPE_LINETO 2

/* このファイル内でのみ用いられる構造体の定義 */
typedef struct {
  WORD    x;
   WORD    y;
} TPoint;

typedef struct {
  int     handle;     /*  ドキュメントのインスタンスハンドル      */
   TPoint  PrevPoint;  /*  以前の点を覚えておく変数                */
   WORD    wID;        /*  コントロールID(でも、今回は利用せず)*/
} TWindowData;

static LRESULT Wm_CreateProc(HWND hwnd,LPCREATESTRUCT cs)
{
  int             handle;
   TWindowData     *p;

  /*  ウインドウ毎の変数領域を確保する    */
   p=malloc(sizeof(TWindowData));
   if(p==NULL)
       return  -1;

  /*  ドキュメントを作成する  */
   <b>handle=CreateDoc()</b>;
   if(handle==-1)
   {
       free(p);
       return  -1;
   }

  /*  ウインドウ毎の変数領域の設定    */
   ZeroMemory(p,sizeof(TWindowData));
   p-&gt;handle   =handle;
   p-&gt;wID      =(WORD)cs-&gt;hMenu;

  /*  ウインドウ毎のメモリ領域に、
       ウインドウ毎の変数領域へのアドレスをセット  */
   SetWindowLong(hwnd,0,(LONG)p);

  return  0;
}
static LRESULT Wm_DestroyProc(HWND hwnd)
{
  TWindowData *p;

  /*  このウインドウの変数領域のアドレスを取得    */
   p=(TWindowData*)GetWindowLong(hwnd,0);

  <b>DestroyDoc(p-&gt;handle)</b>;          /*  保存したデータを廃棄する    */

  free(p);

  return  0;
}

static LRESULT Wm_LButtonDownProc(HWND hwnd,WPARAM fwKeys,WORD xPos,WORD yPos)
{
  TWindowData *p;

  /*  このウインドウの変数領域のアドレスを取得    */
   p=(TWindowData*)GetWindowLong(hwnd,0);

  SetCapture(hwnd);       /*  マウスをキャプチャする  */
   p-&gt;PrevPoint.x=xPos;        /*  マウスの左ボタンを押したときの位置を覚える  */
   p-&gt;PrevPoint.y=yPos;
   return  0;
}

static LRESULT Wm_LButtonUpProc(HWND hwnd,WPARAM fwKeys,WORD xPos,WORD yPos)
{
  HDC         DC;
   TWindowData *p;

  /*  このウインドウの変数領域のアドレスを取得    */
   p=(TWindowData*)GetWindowLong(hwnd,0);


  if(GetCapture()==hwnd)  /*  マウスをキャプチャしているのが、このウインドウである事を確認して*/
   {
       TPoint  forSave;    /*  保存用  */
       DC=GetDC(hwnd);
       MoveToEx(DC,(short)p-&gt;PrevPoint.x,(short)p-&gt;PrevPoint.y,NULL);    /*  DEBUG   */    
           <b>AddLast(p-&gt;handle,TYPE_MOVETO,sizeof(TPoint),&amp;(p-&gt;PrevPoint))</b>;
       LineTo(DC,(short)xPos,(short)yPos);                             /*  DEBUG   */
           forSave.x=xPos;
           forSave.y=yPos;
           <b>AddLast(p-&gt;handle,TYPE_LINETO,sizeof(TPoint),&forSave;)</b>;
       ReleaseDC(hwnd,DC);
       ReleaseCapture();   /*  マウスを開放する    */
   }
   return  0;
}

static LRESULT Wm_MouseMoveProc(HWND hwnd,WPARAM fwKeys,WORD xPos,WORD yPos)
{
  HDC         DC;
   TWindowData *p;

  /*  このウインドウの変数領域のアドレスを取得    */
   p=(TWindowData*)GetWindowLong(hwnd,0);

  if(GetCapture()==hwnd)  /*  マウスをキャプチャしているのが、このウインドウである事を確認して*/  
   {
       DC=GetDC(hwnd);
       MoveToEx(DC,(short)p-&gt;PrevPoint.x,(short)p-&gt;PrevPoint.y,NULL);    /*  DEBUG   */
           <b>AddLast(p-&gt;handle,TYPE_MOVETO,sizeof(TPoint),&amp;(p-&gt;PrevPoint))</b>;  /*  保存    */
       LineTo(DC,(short)xPos,(short)yPos);                             /*  DEBUG   */
       ReleaseDC(hwnd,DC);
       p-&gt;PrevPoint.x=xPos;
       p-&gt;PrevPoint.y=yPos;
           <b>AddLast(p-&gt;handle,TYPE_LINETO,sizeof(TPoint),&amp;(p-&gt;PrevPoint))</b>;  /*  保存    */
   }
   return  0;
}

static LRESULT Wm_PaintProc(HWND hwnd)
{
  PAINTSTRUCT ps;
   int         type;
   TPoint      *Data;
   HDC         PaintDC;
   TWindowData *p;

  /*  このウインドウの変数領域のアドレスを取得    */
   p=(TWindowData*)GetWindowLong(hwnd,0);

  if(GetUpdateRect(hwnd,NULL,TRUE))
   {
       PaintDC=BeginPaint(hwnd,&ps;);
       if(<b>First(p-&gt;handle)</b>)    /*  最初の位置にカーソルを持ってくる    */
       {
           do
           {
               <b>type=GetDataType(p-&gt;handle)</b>;    /*  データタイプを取得  */
               switch(type)        /*  データタイプによって使用するGDI関数を選択    */
               {
                   case    TYPE_MOVETO:
                       <b>Data=(TPoint*)GetData(p-&gt;handle)</b>;
                       MoveToEx(PaintDC,(short)Data-&gt;x,(short)Data-&gt;y,NULL); /*  DEBUG   */
                       break;
                   case    TYPE_LINETO:
                       <b>Data=(TPoint*)GetData(p-&gt;handle)</b>;
                       LineTo(PaintDC,(short)Data-&gt;x,(short)Data-&gt;y);            /*  DEBUG   */      
                       break;
               }
           }
           while(<b>Next(p-&gt;handle)</b>);     /*  カーソルを次に進める    */
       }
       EndPaint(hwnd,&ps;);
   }
   return  0;
}
</pre>
</table>
<p>
 エッセンスは、前回、説明しましたね。構造体を定義し、WM_CREATEメッセージに応答する関数の中で、malloc関数を用いてメモリオブジェクトを確保。これに対するアドレスをウインドウ毎のメモリにSetWindowLong関数を用いてセットする。あと、malloc関数で確保したメモリ領域にアクセスするときは、GetWindowLong関数で、そのアドレスを取得して用いる。WM_DESTROYメッセージに応答して、malloc関数で確保したメモリオブジェクトを破棄するときも同様ですね。
<p>
 実際に、リスト15-3のソースを追っかけると良く分かると思います。
<hr>
<p>
<b>・C15Dllb.Dllを用いるアプリケーションを見てみよう</b>
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト15-4(WinMain.c)
<tr><td>
<pre>
/*
  『C言語で始めるWindowsプログラミング』
   15章のサンプルプログラム
                   Programmed by Y.Kondo

/


#define STRICT
#include &lt;windows.h&gt;
#include "wndproc.h"

/* アプリケーションエントリーポイント */
int WINAPI WinMain(HINSTANCE hInstance,
                 HINSTANCE    hPrevInstance,
                  LPSTR        CmdLine,
                  int          CmdShow)
{
  HWND            hwnd;       /*  メインウインドウのウインドウハンドル    */
   MSG             msg;        /*  メッセージキューから取得したメッセージ  */
   WNDCLASS        wc;         /*  ウインドウクラス登録用の構造体          */
   HINSTANCE       hInstDll;   /*  DLLのインスタンス                    */

  wc.style        =0;
   wc.lpfnWndProc  =WindowProc;
   wc.cbClsExtra   =0;
   wc.cbWndExtra   =0;
   wc.hInstance    =hInstance;
   wc.hIcon        =LoadIcon(NULL,IDI_APPLICATION);
   wc.hCursor      =LoadCursor(NULL,IDC_ARROW);
   wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
   wc.lpszMenuName =NULL;
   wc.lpszClassName="MainWindowClass";
   
   if(RegisterClass(&wc;)==0)               /*  ウインドウクラス登録    */
       return  0;

  <b>hInstDll=LoadLibrary("c15dllb.dll");    /*  DLLをロード          */</b>
   if(hInstDll==NULL)
       return  0;

  hwnd=CreateWindow(  "MainWindowClass",  /*  ウインドウ作成          */  
                       "15章 補講(その2)",
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       NULL,
                       (HMENU)NULL,
                       hInstance,
                       0);
   if(hwnd==NULL)
       return  0;

  ShowWindow(hwnd,CmdShow);           /*  ウインドウの表示        */
   UpdateWindow(hwnd);                 /*  ウインドウの最初の更新  */

  while(GetMessage(&msg;,NULL,0,0))    /*  メッセージループ        */
   {
       TranslateMessage(&msg;);
       DispatchMessage(&msg;);
   }

  <b>FreeLibrary(hInstDll);</b>

  return  msg.wParam;
}
</pre>
</table>
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト15-5(WndProc.c)
<tr><td>
<pre>
/*
  『C言語で始めるWindowsプログラミング』
   15章のサンプルプログラム
                   Programmed by Y.Kondo

/


#define STRICT
#include &lt;windows.h&gt;
#include "wndproc.h"
#include "c15dllb.h"

/*===============================================================================*/

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_CreateProc(HWND,LPCREATESTRUCT);
static LRESULT Wm_DestroyProc(HWND);
static LRESULT Wm_SizeProc(HWND,WPARAM,WORD,WORD);

/* メインウインドウのウインドウプロシージャ */
LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {
       case    WM_CREATE:
           return  Wm_CreateProc(hwnd,(LPCREATESTRUCT)lparam);
       case    WM_DESTROY:
           return  Wm_DestroyProc(hwnd);
       case    WM_SIZE:
           return  Wm_SizeProc(hwnd,wparam,LOWORD(lparam),HIWORD(lparam));
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

/*===============================================================================*/

static LRESULT Wm_CreateProc(HWND hwnd,LPCREATESTRUCT cs)
{
  RECT    ClientRect;
   HWND    hChildWindow;   

  GetClientRect(hwnd,&ClientRect;);

  /*  子ウインドウの作成  */
   hChildWindow=CreateWindow(YK_LINE_CONTROL,
                             "",
                             WS_CHILD|WS_VISIBLE|WS_BORDER,
                             0,0,
                             ClientRect.right/2,ClientRect.bottom,
                             hwnd,
                             (HMENU)1,
                             cs-&gt;hInstance,
                             NULL);
   if(hChildWindow==NULL)
       return  -1; /*  子ウインドウの作成に失敗すれば-1を返す    */

  /*  子ウインドウの作成  */
   hChildWindow=CreateWindow(YK_LINE_CONTROL,
                             "",
                             WS_CHILD|WS_VISIBLE|WS_BORDER,
                             ClientRect.right/2,0,
                             (ClientRect.right-ClientRect.right/2),ClientRect.bottom,
                             hwnd,
                             (HMENU)2,
                             cs-&gt;hInstance,
                             NULL);
   if(hChildWindow==NULL)
   {
       DestroyWindow(GetDlgItem(hwnd,1));
       return  -1; /*  子ウインドウの作成に失敗すれば-1を返す    */
   }

  return  0;
}
static LRESULT Wm_DestroyProc(HWND hwnd)
{
  PostQuitMessage(0);
   return  0;
}

static LRESULT Wm_SizeProc(HWND hwnd,WPARAM fwSizeType,WORD nWidth,WORD nHeight)
{
  MoveWindow(GetDlgItem(hwnd,1),0,0,nWidth/2,nHeight,TRUE);
   MoveWindow(GetDlgItem(hwnd,2),nWidth/2,0,nWidth-nWidth/2,nHeight,TRUE);
   return  0;
}
</pre>
</table>
<p>
 特に問題は無いでしょう。今回のお絵描き子ウインドウは、実用性はまったくありません。またの機会にメッセージを送るだけで、ファイル名などを入力できるウインドウをオープンし、データを保存できるなど、機能の拡張をしたいと思います。
<hr>
<p>
 今回は、前回の捕捉説明の為に、5章で作ったお絵描きプログラムを子ウインドウ化してみました。如何に、ウインドウ毎にデータ領域をあてがうことが煩雑な事か理解できたのではないでしょうか?「MFCを使う方がSDKより簡単」と言うのもまんざら嘘では無さそうですね。
<p>
 まだまだ、説明したい事柄はありますが、Windows3.1の時代からある基本的なコントロールを紹介して、ダイアログボックスと呼ばれるウインドウの説明に移って行こうと思います。
<p>
 次回は、Windowsに組み込まれている基本的なコントロールを紹介します。
<p>
 では、お楽しみに。
<p align="RIGHT">
1999年11月13日<br>
修正1999年11月24日<br>
修正2000年06月04日<br>
<hr>
<p align="RIGHT">
<a href="index.html">目次</a><br>
<a href="from16/chap16.html">次へ</a>
<hr>
<p align="RIGHT">
著作権者:近藤妥

</body>
</html>

  • 最終更新:2018-03-11 04:47:10

このWIKIを編集するにはパスワード入力が必要です

認証パスワード