EternalWindows
ウインドウ管理 / ウインドウクラス

Windowsプログラミングを始めた当初、WinMain関数のコードは重くのしかかるものです。 それは、単純にWinMainで使われている関数や構造体の意味が分からないというよりも、 そもそもWinMainで何をすればよいのかがはっきりしないというのが大部分を占めるでしょう。 しかし、結論から述べると、WinMainで行われる主な処理は2つに絞られます。 1つは、ウインドウをセットアップするための処理で、 これには後述するウインドウクラスの登録やウインドウの作成、 ウインドウの表示といった3つの作業が含まれることになります。 これは一見多いように思えますが、実際にはとりわけ複雑な概念が絡むこともなく、 ただ単純に一連の関数を呼び出すだけで完了することになるため、 基本的には関数や構造体の使い方を覚えるだけで十分といえるでしょう。 もう1つの主な処理であるメッセージループについては、後の節で詳述します。

それでは早速、WinMain関数の内部を見ていきたいと思います。 今回、注目するのは次の部分です。

WNDCLASSEX wc;

wc.cbSize        = sizeof(WNDCLASSEX);
wc.style         = 0;
wc.lpfnWndProc   = WindowProc;
wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;
wc.hInstance     = hinst;
wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName  = NULL;
wc.lpszClassName = szAppName;
wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);

if (RegisterClassEx(&wc) == 0)
	return 0;

このコードはウインドウクラスの登録と呼ばれる部分で、 ウインドウクラスがwc(WNDCLASSEX構造体)にあたり、 登録がRegisterClassEx関数にあたります。 ウインドウクラスとは、ウインドウを作成するための基本的な情報の集まりのことです。 全てのウインドウは何らかのウインドウクラスを基に作られるため、 ウインドウクラスを登録しなければウインドウを作成することはできません。 アプリケーションがRegisterClassExを呼び出したとき、 システムは内部で補足メモリ(後述)を考慮したWNDCLASS構造体を格納できるだけの メモリを確保し、そこに引数で受け取ったWNDCLASS構造体をコピーします。 これが、ウインドウクラスの登録となります。 次に、メンバ変数の初期化を順に見ていきます。

wc.cbSize = sizeof(WNDCLASSEX);

cbSizeは、必ずWNDCLASSEX構造体のサイズを指定します。 サイズが不正である場合は、RegisterClassExの呼び出しに失敗することになります。

wc.style = 0;

styleは、クラススタイルと呼ばれるメンバです。 CreateWindowExで指定するウインドウスタイルとの区別が難しいところですが、 ウインドウスタイルはウインドウの概観を定義するものに対して、 クラススタイルは主にウインドウの内部的要素を定義します。 クラススタイルとして指定できる定数には、次のようなものがあります。

定数 意味
CS_DBLCLKS マウスをダブルクリックした場合、WM_LBUTTONDBLCLKがウインドウプロシージャに送られる。
CS_NOCLOSE 「閉じる」ボタンとシステムメニューの「閉じる」を使用不可にする。 このスタイルは、SetClassLongPtrで動的に置き換えても効果が反映されない。
CS_HREDRAW クライアント領域の幅が変更された際、ウインドウ全体を再描画する。
CS_VREDRAW クライアント領域の高さが変更された際、ウインドウ全体を再描画する。
CS_DROPSHADOW WindowxXPより追加されたスタイル。 詳細は不明だが、ウインドウに影が付き、移動の際に残像が見えるようである。 このスタイルを追加する場合は、SystemParametersInfoでSPI_SETDROPSHADOWをTRUEにし、 _WIN32_WINNTを0x0501として定義しコンパイルする。

クラススタイルが不要な場合は、0を指定して問題ありません。

wc.lpfnWndProc = WindowProc;

lpfnWndProcは、ウインドウプロシージャのアドレスを指定します。 システムは、イベントが発生したウインドウにメッセージを送るとき、 lpfnWndProcに指定されている関数を呼び出すため、 ここに指定する関数はプログラムが作成したWindowProcになります。

wc.cbClsExtra = 0;
wc.cbWndExtra = 0;

これらには、補助メモリを追加するためのサイズを指定します。 補助メモリとはデータ構造体の末尾に追加するメモリのことで、 ここで指定したサイズ分のメモリが内部で確保されることになります。 cbClsExtraはシステムが内部で管理するWNDCLASS構造体に追加するメモリのサイズで、 cbWndExtraはウインドウを表す内部データ構造体に追加するメモリです。 このウインドウを表す内部データ構造体いうのがHWND型で識別されています。

wc.hInstance = hinst;

hInstanceは、WinMainの第1引数を指定します。 これは、インスタンスハンドルとウインドウクラスを関連付ける仕組みです。 このおかげで、アプリケーションはウインドウクラスが必要になったときに、 インスタンスハンドルを要求するGetClassInfoExでWNDCLASSEX構造体を取得できます。 ちなみに、hInstanceにNULLを指定した場合、 RegisterClassExはWinMainの第1引数が指定されたものと解釈します。

wc.hIcon = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);

hIconは、大きいアイコンのハンドルを指定します。 大きいアイコンとは、タイトルバーの左横隅に配置されているアイコンのことです。 LoadImageは、アイコンやカーソル、ビットマップといったリソースをロードする関数で、 第3引数にそのいずれかを表す定数を指定することになります。IMAGE_ICONという定数は、 アイコンをロードすることを意味し、その種類は第2引数に指定します。 IDI_APPLICATIONを指定すると、一般のアプリケーションアイコンとなります。 ちなみに、NULLで初期化した場合も一般のアプリケーションアイコンが表示されます。 残りの引数の詳細は、別の章にて説明します。

wc.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);

hCursorは、ウインドウ内でのカーソルの形状を指定します。 カーソルのロードもアイコンと同じくLoadImageで可能で、 この場合は第3引数がIMAGE_CURSORとなります。第2引数はカーソルの種類を指定する ことになり、IDC_ARROWは一般によく見かける矢印カーソルです。 hCursorをNULLで初期化した場合、カーソルは不定になります。

wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

hbrBackgroundは、ウインドウの背景色を表すブラシを指定します。 ウインドウの背景色とはクライアント領域の色のことで、 たとえば、GetStockObjectという関数の第1引数をBLACK_BRUSHとしたならば、 クライアント領域の色は黒くなります。 ちなみに、次のように定数を代入して初期化する方法も存在します。

wc.hbrBackground = (HBRUSH)(COLOR_ACTIVECAPTION + 1);

COLOR_ACTIVECAPTIONは、システムカラーの参照などに使われる定数で、 このような定数に+1をすれば、その色のブラシが適応されることになります。 GetStockObjectやブラシについては、GDIの章で詳述されています。

wc.lpszMenuName = NULL;

lpszMenuNameは、メニューリソースを識別する文字列を指定します。 これを利用する場合はMAKEINTRESOURCEマクロにメニューのIDを指定し、 その戻り値をlpszMenuNameに指定することになるでしょう。 文字列が不正でないときは、ウインドウの表示と共にメニューも表示されます。

wc.lpszClassName = szAppName;

lpszClassNameは、ウインドウクラスを識別するための文字列を指定します。 ウインドウクラスを登録した後、プログラムはウインドウクラスを ここで指定した文字列で識別することになります。

wc.hIconSm = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);

hIconSmは、小さいアイコンのハンドルを指定することになります。 小さいアイコンとは、最小化時にタスクバー上で表示されるアイコンのことで、 初期化の仕方はhIconと同じになります。

ウインドウクラスのメンバは全て必要になるわけではなく、 また、個々のメンバもそれほど複雑ではないと思われますが、 ウインドウクラスの存在意義事体を理解するのはなかなか難しいものです。 ウインドウクラスにはウインドウの一部を構成するための情報が含まれており、 最終的にはウインドウに関連付けられることになるわけですから、 ウインドウの作成の際に必要な情報をまとめて指定してもよいように思えます。 しかし、その中に自分と同じ属性を持つウインドウ間で共通となりえる情報があるならば、 その情報のみを構造体(ウインドウクラス)にまとめるようにし、 それをウインドウと関連付けた方が効率的です。 このようにすれば、1つのウインドウクラスから複数のウインドウを作成でき、 なにより、ウインドウの作成が大幅に楽になるのです。 これらの意味は、ボタンやリストボックスなどのコントロールを扱う際に強く実感できます。

最後に、ウインドウクラスの破棄について説明します。 ウインドウクラスを破棄するには、UnregisterClassを呼び出します。

BOOL UnregisterClass(
    LPCTSTR lpClassName,
    HINSTANCE hInstance
);

lpClassNameは、破棄するウインドウクラスのクラス名を指定します。 hInstanceは、クラスを作成したアプリケーションのインスタンスハンドルです。 関数が成功すると、0以外の値が返ります。 指定したクラス名を持つクラスが存在しない場合や、 そのクラスで作成されたウインドウがまだ存在するときは0が返ります。

ウインドウクラスを破棄するとは、システムがウインドウクラスに 要していたメモリを開放することと思って構いません。 しかし、ウインドウクラスはアプリケーションの終了時に破棄されることになっているため、 明示的に破棄する必要もありませんし、破棄できるタイミングも限られています。 ウインドウクラスはウインドウを構成する1つの要素ですから、 ウインドウクラスが破棄されては、ウインドウは存在することができません。 つまり、そのクラスで作成されたウインドウが存在する限りは、 ウインドウクラスは破棄できないわけですから、UnregisterClassを呼び出すとすれば、 メッセージループを抜けた後になるのが一般的でしょう。

クラスアトムについて

ウインドウクラスはアプリケーション固有の存在であり、 1つのアプリケーションは同じクラス名を持ったウインドウクラスを複数回登録できません。 クラス名がアプリケーション内のみで一意な存在であるため、 他のアプリケーション内では同一のクラス名のウインドウクラスを登録できますが、 このウインドウクラスも固有であるため、両者に何らかの影響が及ぶようなことはありません。 クラスアトムとは、ウインドウクラスを識別する数値であり、 クラス名と同じようにこれもアプリケーション内でのみ有効です。 クラスアトムがウインドウクラスを識別できる数値であることから、 クラス名を要求するCreateWindowExやUnregisterClassに、 文字列の代わりとして指定できます。

ATOM atom;
・
・
・
atom = RegisterClassEx(&wc);
if (!atom)
	return 0;

hwnd = CreateWindowEx(0, (LPTSTR)MAKELONG(atom, 0), ...);
if (hwnd == NULL)
	return 0;

ATOMの実体はWORD型であり、それをBOOL型のように!を付けて0かどうかを判断するのは、 本来ならば推奨される処理ではないかもしれません。 クラスアトムをクラス名の代わりに指定する場合は、4バイトの型に整形しなければならず、 また、その下位ワードにはクラスアトムが、上位ワードには0を格納しなければならないため、 MAKELONGマクロを上記のように使用することになります。 なお、クラスアトムを取得したい場合は、GetClassLongPtrにGCW_ATOMを指定するか、 GetWindowInfoを呼び出してWINDOWINFO構造体を初期化する方法が考えられます。 WINDOWINFO構造体は、クラスアトムのことをウインドウタイプと呼び、 atomWindowTypeというメンバを持っています。



戻る