3章 Windowsアプリケーションの骨格(その1)

 2章のリスト2-1をもう一度見てみましょう。下のリスト3ー1は、リスト2-1を見やすく修飾したものです。合計9個の関数が用いられ、2つの関数が定義されていますね。
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト3ー1
<tr><td>
<pre>
/*Windowsプログラミングの最初の一歩*/

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

/*ウインドウプロシージャのプロトタイプ宣言*/
LRESULT CALLBACK WindowProc(HWND,UINT,WPARAM,LPARAM);

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

  wc.style        =0;
   wc.lpfnWndProc  =<b><i><u>WindowProc</u></i></b>;
   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="Hello";
   
   if(<b><i><u>RegisterClass</u></i></b>(&wc;)==0)       /*  ウインドウクラス登録    */
       return  0;

  hwnd=<b><i><u>CreateWindow</u></i></b>(  "Hello",    /*  ウインドウ作成          */  
                       "Hello World",
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       NULL,
                       (HMENU)NULL,
                       hInstance,
                       0);
   if(hwnd==NULL)
       return  0;

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

  while(<b><i><u>GetMessage</u></i></b>(&msg;,NULL,0,0))    /*  メッセージループ        */
   {
       <b><i><u>TranslateMessage</u></i></b>(&msg;);
       <b><i><u>DispatchMessage(&msg;)</u></i></b>;
   }
   return  msg.wParam;
}

/* ウインドウプロシージャ */
LRESULT CALLBACK <b><i><u>WindowProc</u></i></b>(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {
       case WM_DESTROY:
           <b><i><u>PostQuitMessage</u></i></b>(0);
           return  0;
   }
   return  <b><i><u>DefWindowProc</u></i></b>(hwnd,message,wparam,lparam);
}
</pre>
</table>

<p>
 最小のWindowsアプリケーションでも、こんなに大きくて複雑な理由は2つ挙げられると思います。一つは、Windowsが<b><i><u>マルチタスクOS</u></i></b>であり、<b><i><u>同時に複数回同じアプリケーションの起動を許している</u></i></b>事。もう一つは、Windowsアプリケーションは、<b><i><u>事象駆動型</u></i></b>である事です。
<p>
 さて、リスト3-1を見ると、main関数がありません。代わりにWinMainという関数がありますね。名前からして、Windowsアプリケーションのメイン関数(<b><i><u>アプリケーションエントリーポイント</u></i></b>)を彷彿させますね。そうです。これが、従来のC言語プログラムのmain関数と同じ働きをするのです。ここで、何を行っているかは後程説明します。
<p>
 もう一つ。WindowProcなる名前の関数があります。この関数は<b><i><u>ウインドウプロシージャ</u></i></b>と呼ばれ、ウインドウに対してユーザーが行った行為に対する反応を定義します。言うならば、VBなどのRADツールは、主にこの部分だけを書けば良いようになっていると言えますね。そして、この関数の名前は、たしかにWinMainの中で関数へのアドレスとして用いられていますが、普通の関数呼び出しスタック構造を採っていません。これについては、後程説明します。
<p>
 では、Windowsアプリケーションのメイン関数WinMainから制御の流れを理解することに留意して見ていきましょう。
<hr><!-------------------------------------------------------------------------->
<p>
<b><big>・WinMain</big></b>
<p>
 WinMain関数は、必ずこの形で、定義されなければいけません。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
int WINAPI WinMain(<b><i>HINSTANCE</i></b> hInstance,<b><i>HINSTANCE</i></b> hPrevInstance,<b><i>LPSTR</i></b> CmdLine,int CmdShow)
</table>
<p>
 まず、いきなりWINAPIという見慣れないトークン。これは、ヘッダファイルの中で
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>#define WINAPI __stdcall
</table>
<p>
と宣言されています。従って、WINAPIと言うのは、ANSI-Cではなく処理系独自の予約語__stdcallの事ですね。普通、C言語は引数の数はプログラミング言語PASCALの様な厳格な決まりはありません。つまり、引数を3つしか取れない関数に4つの引数を渡してもきちんと動くと言うことです。この時、余分な右側の引数は無視されます。これは、C言語が関数に引数として情報を渡す時、スタックに引数の右側からプッシュしていき、呼び出し側の関数が、スタックポインタを元に戻すという実装上の性質から来るものです。__stdcallという予約語は、このC言語の関数呼び出し慣例を変えるもので、__stdcallを指定した関数は、それ自身がスタックスタックポインタを元に戻します。したがって、DOSの時のmain関数の様に、
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
int main()<br>
int main(int argc)<br>
int main(int argc,char* argv[])<br>
</table>
<p>
のどれでも良いのではなく、<b><i><u>必ず全ての引数を書かなければいけません</u></i></b>。
<p>
 さて、最初の引数が、これからアプリケーションを構築していく上で一番重要になるでしょう。最初の引数の型HINSTANCEは、<b><i><u>インスタンスハンドル</u></i></b>と呼ばれる物を表現する型です。インスタンスとは何でしょうか?ハンドルとは何でしょうか?
<p>
 インスタンスとは、英語で実例と言う意味です。たとえば、***学校の学生といえば複数いますね。その例として●●君が挙げられます。インスタンスとは●●君の事で、学生証の番号が、まさにインスタンスハンドルといえます。何故、このような事をするかというと、先の説明のように、Windowsは同じアプリケーションを同時に複数回起動出来るからです。たとえば、メモ帳といえども、同時に複数回起動できるので、名前をもって個々の起動しているアプリケーションを表現できないのです。
<p>
 ハンドルとはどのようなものでしょうか?Windowsでは、ハンドルという用語は頻繁に登場します。DOSの時代には、ファイルハンドルぐらいしかお目にかかれませんでしたが、とにかくWindowsでは、何々ハンドルという用語が頻繁に登場します。ハンドルというのは、OSが管理している資源を示す一意の整数です。実装は知るすべもありませんが、イメージとして、配列の添え字の様な物です。Windows95/98/NTは32ビットOSですので、ハンドルは全て32ビット整数です。
<p>
 さて、リスト2-1や3-1を見て、最初の
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
#define STRICT<br>
#include &ltwindows.h;&gt;
</table>
<p>
を見て「おやっ」と思った方がいるのではと思います。すでに申しましたように、Windowsには何々ハンドルという物が多く登場します。これらは基本的に32ビット整数ですので、別の物に対するハンドルでも代入可能で、コンパイラはこれをチェックできません。しかし、Windows3.1以降、ヘッダファイルの改良でコンパイル時に明らかなハンドルの代入ミスなどをチェックする工夫がなされました。STRICTとは、この機能を使うことをコンパイラに通知する物です。従って、あらゆるヘッダファイル取り込みに先立って定義する必要があります。STRICTを定義することにより、型チェックが厳密に行われ、デバッグが容易になります。必ず、STRICTを定義するようにしましょう。
<p>
 2番目の引数は、16ビット時代のWindowsからの痕跡で、32ビットになってからは意味を持たなくなりました。32ビット環境では、常にNULLです。
<p>
 3番目の引数は、コマンドラインパラメータの文字列です。パラメーター部分のみで、アプリケーションのファイル名は含まれません。
<p>
 4番目の引数は、起動時にどのような状態(最小の状態など)で起動するかを指定する引数です。
<p>
 さて、WinMain関数の引数についての一巡の説明が終わりました。しかし、インスタンスハンドルを始めハンドルに関して、かなり大嘘をついています。これは、32ビット環境では、各プロセスが独立したアドレス空間に置かれるという性質から来ています。機会があれば、この辺に関して詳細な説明をします。今のところ、この様な理解で十分でしょう。
<p>
 次に、WinMain関数の内部の処理の説明に移ります。
<hr><!------------------------------------------------------------------------>
<p>
<b><big>・WinMain内部の処理</big></b>
<p>
 WinMain関数の内部で行っている処理は、大きく3つです。つまり、<b><i><u>ウインドウクラスの登録</u></i></b>、<b><i><u>ウインドウの作成と表示</u></i></b>、そして、<b><i><u>メッセージの取り込みと配布(メッセージループ)</u></i></b>です。
<p>
 最初に行うのは、<b><i><u>RegisterClass</u></i></b>関数を用いた<b><i><u>ウインドウクラス</u></i></b>の登録です。ウインドウクラスというのは、<b><i><u>ウインドウの動きの定義</u></i></b>の事です。登録の仕方は、<b><i><u>WNDCLASS構造体</u></i></b>型の変数の各メンバに値をセットして、RegisterClass関数に変数のアドレスを渡すだけです。WNDCLASS構造体を見てみましょう。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
typedef struct _WNDCLASS {
  UINT    style; 
   WNDPROC lpfnWndProc; 
   int     cbClsExtra; 
   int     cbWndExtra; 
   HANDLE  hInstance; 
   HICON   hIcon; 
   HCURSOR hCursor; 
   HBRUSH  hbrBackground; 
   LPCTSTR lpszMenuName; 
   LPCTSTR lpszClassName; 
} WNDCLASS;</pre>
</table>
<p>
 WNDCLASS構造体には多くのメンバがありますが、<b><i><u>制御の流れを理解する上で重要な物</u></i></b>は2つだけです。つまり、<b><i><u>lpfnWndProc</u></i></b>と<b><i><u>lpszClassName</u></i></b>です。
<p>
 lpfnWndProcには、後から説明するウインドウプロシージャの名前が代入されていますね。この講座を読んでいる方は、C言語の文法の説明は必要無いと思います。しかし、念のために説明しますと、C言語では関数名は、関数のエントリーポイントのアドレスです。つまり、RegisterClass関数を用いて登録するウインドウクラスなる物には、ウインドウプロシージャのエントリーポイントのアドレスが含まれていると言うことです。
<p>
 もう一つ、lpszClassNameには、クラス名をあらわす文字列へのポインタを代入します。クラス名は任意の文字列を指定できます。ここで、指定したクラス名は、後で説明するウインドウの作成に用います。
<p>次に行うのは、<b><i><u>CreateWindow</u></i></b>関数を用いたウインドウの作成です。ウインドウクラスは、ウインドウの動きの定義でしたよね。今度行うウインドウの作成というのは、<b><i><u>ウインドウの外観の定義とウインドウクラス(ウインドウの動き)とのリンク</u></i></b>の事です。CreateWindow関数を見てみましょう。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
HWND CreateWindow(

  LPCTSTR  lpClassName,
   LPCTSTR  lpWindowName,
   DWORD    dwStyle,
   int      x,
   int      y,
   int      nWidth,
   int      nHeight,
   HWND     hWndParent,
   HMENU    hMenu,
   HANDLE   hInstance,
   LPVOID   lpParam
  );
</pre>
</table>

<p>
 CreateWindow関数の引数で、<b><i><u>制御の流れを理解する上で重要な物</u></i></b>は2つです。1つは、第一引数の<b><i><u>lpClassName</u></i></b>。もう1つは、<b><i><u>hInstance</u></i></b>です。この2つの引数により、このウインドウは、ユーザーの操作に応じてどのような動作をし、どのインスタンスに属するのかが決まるのです。
<p>
 CreateWindow関数の引数についてもう少し詳細に見ていきましょう。第二引数のlpWindowNameは、タイトルバーに表示したい文字列へのポインタを代入します。第三引数のdwStyleというのは、ウインドウの外観を決める物で、タイトルバーの有無やスクロールバーの有無などをここで決めることが出来ます。次のx、yというのは、ウインドウの作成位置です。Windowsには色々な座標系があり、混乱の元になっています。このx、yというのは<b><i><u>スクリーン座標系</u></i></b>の座標で、パソコンの画面の左上が原点(0,0)でX軸は右方向が正、Y軸は下方向が正です。そして、単位はピクセルです。それゆえ、VGAの画面の右下の座標は(639,479)となります。そして、次のnWidth、nHeightは、ウインドウのサイズです。単位はピクセルです。さて、リスト3-1で用いられているCW_USEDEFAULTというのは何でしょうか?これは、作成するウインドウの位置と大きさをOSに任せるという意味です。特に作成する位置と大きさにこだわらないのでしたら、CW_USEDEFAULTがお勧めです。次、hWndParent。これは、ウインドウの親子関係のリンクをする為の物です。いずれこの講座で説明しますので、保留にしておきましょう。次、hMenu。これは、ウインドウにメニューをつける場合の物です。今回は、ご覧のようにメニューはありませんので、NULLです。そして、最後の引数のlpParam。これは、ウインドウを作成する時、ウインドウプロシージャにデータを渡す為の物です。これも、いすれこの講座で使い方を説明します。
<p>
 さて、CreateWindow関数は、ウインドウの作成に成功した場合は、ウインドウハンドルを返します。今後、このウインドウに対する操作は、このウインドウハンドルを用いて行います。
<p>
 ウインドウの作成に成功しても、ウインドウが画面に表示されるわけではありません。そこで、<b><i><u>ShowWindow</u></i></b>関数を用いて表示します。この関数の第一引数は、表示したいウインドウのウインドウハンドルです。第二引数は、どのような状態で表示(あるいは非表示)するのかを決定する物です。今回の場合、メインウインドウとなる物なので、WinMain関数の第四引数をそのまま用いた方が、ユーザーから見た場合、素直なアプリケーションになります。さて、ShowWindow関数の直下のUpdateWindow関数に関しては制御の流れを理解する上で重要な意味を持ちませんので、ここでの説明は省きます。
<p>
 次に、最後のステップであるメッセージループの説明をします。メッセージループというのは事象駆動型アプリケーションの心臓部です。メッセージループが心臓ならそこから全身に送り出されるものは何でしょうか?これは、メッセージです。
 Windowsは、個々のウインドウに起こった出来事(事象:event)をメッセージに変換して付属情報とともにこのウインドウが属しているインスタンスのメッセージキュー(キューは日本語で待ち行列の意味です)に置きます。これをメッセージループが吸い上げ、アプリケーション全体に送り出すのです。
<p>
 メッセージキューに置かれたメッセージは、<b><i><u>GetMessage</u></i></b>関数によって吸い上げます。GetMessage関数を見てみましょう。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
BOOL GetMessage(
  LPMSG  lpMsg
   HWND  hWnd,
   UINT  wMsgFilterMin,
   UINT  wMsgFilterMax 
  );
</pre>
</table>
<p>
 4つの引数を持ちますが、第一引数が最も重要で、メッセージキューからの情報をここで受け取ります。他の引数は、NULLまたは0で、一切のフィルター無しに、全てのメッセージを吸い上げると理解しておいてください。受け取るメッセージは<b><i><u>MSG構造体</u></i></b>の変数です。このMSG構造体を見てみましょう。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
typedef struct tagMSG {

  HWND   hwnd;
   UINT   message; 
   WPARAM wParam; 
   LPARAM lParam; 
   DWORD  time; 
   POINT  pt; 
} MSG;
</pre>
</table>
<p>
 <b><i><u>制御の流れを理解する上で重要</u></i></b>なメンバは、<b><i><u>hwnd</u></i></b>です。つまり、GetMessage関数が吸い上げたメッセージには、どのウインドウで発生したメッセージかを識別できる情報が含まれているのです。しかし、GetMessage関数で吸い上げたメッセージを直接利用することは希です。筆者は、一度も利用したことがありません。
<p>
 さて、<b><i><u>GetMessage関数は、WM_QUITというメッセージを吸い上げない限り、TRUEを返します</u></i></b>。したがって、リスト3-1のwhileループが終了する時は、GetMessage関数がWM_QUITを吸い上げた時です。では、メッセージキューに吸い上げるべきメッセージが無かった場合どうなるのでしょうか?答えは、「CPU使用権を他のインスタンス(プロセス)に譲渡する」が正解。むしろ、Windows3.1までのWindowsは、この関数を利用して複数のインスタンス(タスク)がCPUを共有していました。このため、お行儀の悪いアプリケーションは、なかなかGetMessage関数を呼ばないために、システム全体が止まってしまう事がありました。しかし、Windows95/98/NTの様な32ビットWindowsでは、OSが強制的にお行儀の悪いアプリケーションからCPU使用権を奪い取れるので、システム全体が止まることは無くなりました。
<p>
 さて、GetMessage関数で吸い上げたメッセージは、物理的です。つまり、どのキーが押され、どのキーが放されたといった物です。これをより論理的な文字情報のメッセージに変換してメッセージキューに置くのが<b><i><u>TranslateMessage</u></i></b>関数です。リスト3-1では、文字は扱っていませんので、この関数を削除しても動作は変わりません。
<p>
 GetMessage関数は、同一のインスタンスに属するウインドウで捕らえたメッセージを一手に吸い上げます。しかし、メッセージに応答するのはインスタンス単位ではなく、ウインドウ単位です。そして、各ウインドウにCreateWindow関数で結び付けられたウインドウクラスに属するウインドウプロシージャがメッセージを処理します。言い換えるなら、GetMessage関数が吸い上げたメッセージに応じて、適切なウインドウプロシージャに制御を渡すのです。これを行うのが、<b><i><u>DispatchMessage</u></i></b>関数です。つまり、WinMain関数から見た場合、ウインドウプロシージャは間接的に呼ばれているのです。ゆえに、概念的に、下のような呼び出しスタックになります。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
WinMain関数<br>
DispatchMessage関数<br>
ウインドウプロシージャ<br>
</table>
<p>
 それゆえ、ウインドウプロシージャは処理を終えるとDispatchMessage関数に制御を戻し、DispatchMessage関数はWinMain関数に制御を戻し、while文によるループが続くわけです。
<p>
 さて、ウインドウプロシージャの説明に移る前に覚えておいて欲しいことは、以前に説明した次のことです。GetMessage関数は、WM_QUITメッセージを吸い上げたら、FALSEを返す。したがって、これによりwhileループから抜け、アプリケションが終了する。いいですね。
<hr>
<p>
<b><big>・ウインドウプロシージャ</big></b>
<p>
 ウインドウプロシージャとは、ウインドウの動きを定義する関数です。たとえば、ペイントブラシは、ブラシを選んで、ウインドウ内部にマウスカーソルを持っていき、マウスの左ボタンを押しながらマウスを動かせば、線が描画されます。この様なウインドウの動作を定義する関数がウインドウプロシージャなのです。
<p>
 ウインドウプロシージャはヘッダファイルWINUSER.Hで以下のように宣言されています。引数が、以前に紹介したMSG構造体のメンバの部分集合ですね。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
</pre>
</table>
<p>
 では、私が定義したウインドウプロシージャWindowProcの説明に入ります。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
</pre>
</table>

 LRESULTという型は、単なる32ビット整数です。この直後にCALLBACKというトークンがありますね。これの意味は、WINAPIとまったく同じです。WINDEF.Hで__stdcallと定義されています。

 第一引数hwndは、動作を起こさねばならないウインドウのウインドウハンドルです。何故ウインドウハンドルという情報が、ウインドウプロシージャに必要かと申しますと、ウインドウクラスを複数のウインドウで共有できるからです。リスト3-1で示したアプリケーションのクラス“Hello”は、一つのウインドウでしか用いられていませんが、<b><i><u>一般に1つのウインドウクラスは複数のウインドウで共有されます</u></i></b>。だから、どのウインドウで生じたメッセージかをウインドウプロシージャが知る必要があるのです。しかし、プログラミング経験のある方なら気がつくように、この場合、ウインドウプロシージャが各ウインドウ毎に状態の保存をしておく必要があります。この時、ちょっと複雑な処理を必要としますので、当分の間、この講座では、ユーザー定義のウインドウクラスの共有はしないことにします。また、ここまで読んできてよく理解されている方は、<b><i><u>複数のウインドウクラスが、1つのウインドウプロシージャを共有できる</u></i></b>事にも気がついているはずですね。この場合も、この講座では当分の間、考えないことにします。

 第二引数は、UINT型の整数です。UINTというのはunsigned intの事です。これがウインドウプロシージャにとってメッセージとなるのです。GetMessage関数が吸い上げるメッセージはMSG構造体型の変数であった事と区別しておいてください。さて、整数と申しましても2とか3などのような物でなく、WM_LBUTTONDOWNと?WM_COMMANDの様なシンボリックな物です。ちなみに、WM_LBUTTONDOWNはウインドウのクライアント領域(いずれ、この講座で説明します)内にマウスカーソルがある時にユーザーがマウスの左ボタンを押した時に発生し、WM_COMMANDというのは、ユーザーがメニュー項目を選択したり、クライアント領域内に設けられたボタンを押した時などに発生します。

 第三引数と第四引数は、第二引数で表されたメッセージの付加情報を表す物です。従って、これの意味するものは第二引数の値(メッセージ)によって異なります。WPARAM型のwparamというのは16ビットの時代は16ビット整数(unsigned short値)でLPARAM型のlparamは32ビット整数(long値)でした。しかし、Windowsが32ビットになった今、どちらも32ビット整数です。


・ウインドウプロシージャの内部処理

 さて、リスト3-1のウインドウプロシージャはWM_DESTROYというメッセージにのみ応答しています。それ以外のメッセージ(いや、自分が特に関心の無いメッセージと言った方が適切か?)が入ってきた場合は、<b><i><u>DefWindowProc</u></i></b>関数に全ての引数を渡して、この関数の戻り値をそのままウインドウプロシージャの戻り値としていますね。これが、これから作成するウインドウプロシージャに共通することです。

 2章とこの章で、さんざん最小のアプリケーションなのにプログラムのサイズが大きいと言いましたが、良く考えてみてください。もし、DOSの時代の様に全部自前で、最小のアプリケーションと同様のアプリケーションを組むとなると、リスト3-1の様な規模のプログラムサイズで済んだでしょうか?こう考えると、リスト3-1のプログラムサイズは極めて小さいと言わねばなりません。このようにプログラムを小さくしているのがDefWindowProc関数なのです。<b><i><u>オブジェクト指向プログラミング</u></i></b>の言葉を借りるならば、アプリケーションプログラマが作るウインドウプロシージャは、DefWindowProc関数を<b><i><u>継承</u></i></b>した物と言えますね。つまり、<b><i><u>ユーザー(プログラマ)定義のウインドウプロシージャは一種のDefWindowProc関数と言えます</u></i></b>。まさに<b><i><u>is-a関係</u></i></b>ですね。

 さて、唯一関心を持って自前で処理するメッセージ、WM_DESTROYとそこで行っている処理の説明をしましょう。WM_DESTROYというのは、ウインドウが破壊された後に、Windowsがウインドウプロシージャにダイレクトに送ってくるメッセージです。ウインドウの破壊に関する説明は今は省きますが、大事なのは、<b><i><u>ウインドウプロシージャは必ずしもDispatchMessage関数から概念的に直接呼ばれるとは限らない</u></i></b>ということです。これに関しては、この講座で用語とともにきちんと説明します。アプリケーションユーザーとしてウインドウを破壊する方法は皆さんご存知ですね。タイトルバー右の×のボタンを押せば、ウインドウが破壊されます。さて、問題のウインドウはどうでしょうか?あのウインドウが破壊される時は、すなわちアプリケーションが終了する時です。そこで、メッセージループの終了条件を思い出してください。メッセージキューにWM_QUITが入っておればいいのですね。そして、これを行う関数が<b><i><u>PostQuitMessage</u></i></b>関数です。この関数は、メッセージキューにWM_QUITメッセージを置き、すぐさま、ウインドウプロシージャの次の処理に制御を戻します。すなわち、リスト3-1の場合、0を戻り値としてウインドウプロシージャを抜けるのです。そして、破壊されるに至った全ての関数呼び出しスタックをたどって、WinMain関数に制御を渡します。つまり、メッセージループに戻るわけです。そして、次のサイクルでGetMessage関数がWM_QUITメッセージを吸い上げ、メッセージループが終了し、WinMain関数は普通のmain関数のようにスタートアップルーチンに制御を戻し、最後にWindowsに制御を戻して、アプリケーションは完全に終了します。


 どうでしたか?Windowsアプリケーションのプログラムは、初心者にとって極めて制御の流れが読みにくい物と思い、制御の流れの概略を理解する事に重点を置いて説明したつもりです。どうか、この説明を元にSDKのマニュアルでこの章で登場した各関数やメッセージを調べて理解を深めてください。

 また、ここまでの説明で挫折した方もご安心ください、とにかく理解できなくても、リスト3-1のプログラムを拡張する事で、アプリケーションを組みはじめられます。ただ、分からなくなった時、いつも、この時点に戻り理解を深めるという事を留意していただければ結構と思います。この講座も、当分の間、この方針で進めていきます。

1999年1月6日
加筆修正:1999年1月7日


著作権者:近藤妥

  • 最終更新:2018-03-12 02:59:43

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

認証パスワード