パソコン活用研究シリコンバレー(C、C++、の活用研究)

Windowsプログラムの正しい雛形(前編)
--ウィンドウクラス構造体の設定と登録--
1 Windowsプログラムの雛形

Windowsプログラムの正しい雛形について、まず見てみたいと思います。”正しい”と言ったからには”正しくない”Windowsプログラムもあるということですが、実際には、”正しくない”プログラムと比較してみると、Windowsプログラムの理解が深まります。

そんなわけで、”正しい”Windowsプログラムだけを説明しても、なかなかピンとこないこともあると思いますが、”正しくない”プログラムと比較するうちに理解できてきますので、とりあえず教科書的に”正しい”Windowsプログラムを説明してみます。

WindowsプログラムはDOSプログラムとはまったく違うので、ざっと読んでみても、さっぱりわからん、という感じかもしれません。DOSプログラムの経験がある場合は、とりあえずウィンドウを表示を見たほうがいいかもしれません。そちらは、”正しくない”Windowsプログラムということになりますが、DOSプログラムの経験があると理解しやすいでしょう。それからWindowsの正しい雛形を見直すと、よく理解できるようになると思います。


”正しい”Windowsプログラムは、エントリポイント(プログラムの開始)である(1)WinMainと、Windowsから呼び出されて渡されたメッセージを処理する(2)ウインドウプロシ−ジャ(通常WinProcと書く)から成り立ちます。

そして(1)WinMainの中は、
(A) ウインドウクラスの登録
(B) ウィンドウの作成
(C) ウインドウの表示
(D) メッセージループ
の4つのパートから成り立ちます。

枠組みだけざっとおさらいしてみましょう。

//(1)Winmain
WinMain (
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
{
}
// ウィンドウクラス構造体を設定

//(A) ウインドウクラスの登録
RegisterClassEx

// (B)ウインドウを作成
CreateWindow

//(C) ウインドウを表示
ShowWindow
UpdateWindow

//(D) メッセージループ

//(2) ウィンドウプロソジャ
WndProc (
HWND hWnd,
UNIT message,
WPARAM wParam,
LPARAM lParam )
{
}




(A)ウインドウクラスの登録というのがまずピンとこないかもしれませんが、ウインドウのマウスカーソルの種類や、アイコン、背景色など、そのウインドウの性質や定義をWindowsが管理するデータベースに登録します。
しかし、ウインドウクラスを登録しただけでは、まだウインドウの実態はメモリー上にありません。次に(B)ウインドウの作成と(C)表示を行って、初めてウインドウが作成され表示されます。

では、所謂、”正しい”雛形というものを具体的にみてみましょう。
以下、Visual C++で「Win32 Application」を選択し、そのうち「空のプロジェクト」を選択した時に自動的に生成される、ただウインドウを表示するだけの空っぽのプログラムです。Visual BasicだとただFormコントロールを配置するだけなのに、C++では、ウインドウを出すだけでこれだけのコードを書くことになります。ここで挫折した人も少なからずいると思いますが、おじさんもこの壁を乗り越えるのに何度も途中でくじけました。
これが、いちおうWindowsプログラムの正しい雛形と考えて下さい。

#include "windows.h"

// 関数のプロトタイプ宣言
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// エントリポイント
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
{
WNDCLASSEX wcex; // ウインドウクラス構造体
HWND hWnd; // ウインドウハンドル
MSG msg; // メッセージ構造体

// ウィンドウクラス構造体を設定します。
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "ModelApp";
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

// ウインドウクラスを登録します。
RegisterClassEx(&wcex);

// ウインドウを作成します。
hWnd = CreateWindow(wcex.lpszClassName, // ウインドウクラス名
"Cursor", // キャプション文字列
WS_OVERLAPPEDWINDOW, // ウインドウのスタイル
CW_USEDEFAULT, // 水平位置
CW_USEDEFAULT, // 垂直位置
CW_USEDEFAULT, // 幅
CW_USEDEFAULT, // 高さ
NULL, // 親ウインドウ
NULL, // ウインドウメニュー
hInstance, // インスタンスハンドル
NULL); // WM_CREATE情報

// ウインドウを表示します。
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

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

// 戻り値を返します。
return msg.wParam;
}

// ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
// メッセージの種類に応じて処理を分岐します。
switch (message)
{
case WM_DESTROY:
// ウインドウが破棄されたときの処理
PostQuitMessage(0);
return 0;
default:
// デフォルトの処理
return DefWindowProc(hWnd, message, wParam, lParam);
}
}


2 ウインドウクラスの登録

まず ウインドウクラス 構造体にどんなメンバがあるのかみてみましょう。
クラスとかメンバとかの言葉になじみがないと、もうひとつ頭の中がすっきりしないと思いますが、オブジェクト指向に慣れてくると、はっきりとわかってきますので、その時にまた見直して下さい。

typedef struct _WNDCLASS {
 UINT style, // ウィンドウクラスのスタイル
 WNDPROC lpfnWndProc, // ウィンドウプロシージャのコールバック関数
 int cbClsExtra, // 余分のウィンドウクラスを指定する場合指定。
 int cbWndExtra, // 余分のウィンドウを作成する場合に指定。
 HANDLE hInstance, // アプリケーションハンドルを指定。
 HICON hIcon, // アイコンハンドルを指定。
 HCURSOR hCursor, // カーソルハンドルを指定。
 HBRUSH hbrBackground, // 背景を塗りつぶすブラシを指定。
 LPCTSTR lpszMenuName, // メニューリソース名を指定
 LPCTSTR lpszClassName // クラス名を指定。
} WNDCLASS;

それぞれのメンバの説明です。

メンバ 解説
style ウィンドウクラスのスタイルを指定します。CS_VREDRAW (垂直方向にウインドウのサイズが変更されたら再描画する)と CS_HREDRAW(水平方向にウインドウのサイズが変更されたら再描画する) をorで指定しておくのが一般です。
lpfnWndProc ウィンドウプロシージャの関数ポインタ(関数の入り口のアドレス)を指定します。CやC++では関数名がその関数のポインタになります。WNDPROCはlong型です。
cbClsExtra 次に指定するウィンドウクラスのための補助領域のサイズを指定します。使用しない場合は、0 を指定します。
cbWndExtra 次に作成するウィンドウのための補充領域割のサイズを指定します。使用しない場合は、0 を指定します。
hInstance アプリケーションハンドルを指定します。WinMain の一番目の引数を渡せばいいでしょう。
hIcon アイコンのハンドルを指定します。
hCursor カーソルのハンドルを指定します。
hbrBackground 背景を塗りつぶすブラシハンドルを指定します。例題は、COLOR_WINDOW + 1を設定していますが、これは白色です。(HBRUSH)はlong型の別名です。実際はGetStockObject 関数を使って設定する方法が多くとられます。この関数を使うことによって、あらかじめ OS にストックしてある GDI (Graphics Device Interface) オブジェクトを使うことが出来ます。
lpszMenuName メニューリソースの名前を指定します。
lpszClassName ウィンドウクラス名の文字列を指定します。

ウィンドウクラス構造体の定義をしたら、次に RegisterClass 関数でウィンドウクラスを登録します。引数には、WNDCLASS 構造体のポインタを指定します。
RegisterClass(&wcex);
ウィンドウクラス構造体のポインタ(&wcex)を引数にしています。

登録するという意味がわかりにくいかもしれませんが、OS(Windows)が管理するメモリー上のデータベースに格納されるということになります。RegisterClass関数で登録した段階では、まだデータベースに型が登録されただけなので、メモリ上にはウインドウの実体はありません。メモリー上にウインドウの実体ができるのは、CreatWindow関数によって、ウインドウを作成してからです。これは次に説明したいと思います。

このRegisterClass (RegisterClassExも同様) 関数は失敗すると 0 を返します。成功すると 0 以外の値を返します。
上記の雛形プログラムはRegisterClass関数の戻り値のチェックをしていませんが、以下のように戻り値をチェックしたほうが安全でしょう。
if(!RegisterClassEx(&wcex)) { return 0; }



WNDCLASS構造体の拡張版として、WNDCLASSEX 構造体があり、一般的にはこちらのWNDCLASSEX構造体を使います。上記の雛形プログラムもWNDCLASSEXを使用しています。WNDCLASSEX は、WNDCLASS を拡張したものです。次のように定義されています。

typedef struct _WNDCLASSEX {
    UINT cbSize, 
    UINT style, 
    WNDPROC lpfnWndProc, 
    int cbClsExtra, 
    int cbWndExtra, 
    HANDLE hInstance, 
    HICON hIcon, 
    HCURSOR hCursor, 
    HBRUSH hbrBackground, 
    LPCTSTR lpszMenuName, 
    LPCTSTR lpszClassName, 
    HICON hIconSm
} WNDCLASSEX;

この構造体は、cbSize メンバと hIconSm メンバが新しく加わっています。cbSize には、この構造体自身のバイト数を指定します。 将来ウインドウクラスにメンバが追加されても対応できるように、将来の拡張性を考えてこのようになっています。
hIconSm には小さいアイコン (16 x 16 ビット) を指定します。このメンバが NULL の場合は、hIcon メンバで指定されたアイコンが使われます。WNDCLASSEX は、RegisterClassEx によって登録します。

TopPage