「トップページ」の編集履歴(バックアップ)一覧に戻る

トップページ - (2008/08/30 (土) 10:03:01) のソース

●Win32API(C言語)編 第2章 ウィンドウを表示する
'2007/2/4 GetMessage() が -1 を返す可能性に対応。
'2006/12/18 _Tマクロを使うように修正。
○ウィンドウを表示してみる

では、ウィンドウを表示してみましょう。と、言うのは簡単ですが、プログラム的には非常に長くなります。ウィンドウに関しては、細かく色々と設定できますが、とりあえずはデフォルト設定のウィンドウを表示させてみます。

#include <windows.h>
#include <tchar.h>

// プロトタイプ宣言
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);


// 開始位置
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd)
{
	WNDCLASSEX wc;
	HWND hWnd;
	MSG msg;

	// ウィンドウクラスの情報を設定
	wc.cbSize = sizeof(wc);               // 構造体サイズ
	wc.style = CS_HREDRAW | CS_VREDRAW;   // スタイル
	wc.lpfnWndProc = WndProc;             // ウィンドウプロシージャ
	wc.cbClsExtra = 0;                    // 拡張情報1
	wc.cbWndExtra = 0;                    // 拡張情報2
	wc.hInstance = hInst;                 // インスタンスハンドル
	wc.hIcon = (HICON)LoadImage(          // アイコン
		NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON,
		0, 0, LR_DEFAULTSIZE | LR_SHARED
	);
	wc.hIconSm = wc.hIcon;                // 子アイコン
	wc.hCursor = (HCURSOR)LoadImage(      // マウスカーソル
		NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR,
		0, 0, LR_DEFAULTSIZE | LR_SHARED
	);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // ウィンドウ背景
	wc.lpszMenuName = NULL;                     // メニュー名
	wc.lpszClassName = _T("Default Class Name");// ウィンドウクラス名
	
	// ウィンドウクラスを登録する
	if( RegisterClassEx( &wc ) == 0 ){ return 1; }

	// ウィンドウを作成する
	hWnd = CreateWindow(
		wc.lpszClassName,      // ウィンドウクラス名
		_T("Sample Program"),  // タイトルバーに表示する文字列
		WS_OVERLAPPEDWINDOW,   // ウィンドウの種類
		CW_USEDEFAULT,         // ウィンドウを表示する位置(X座標)
		CW_USEDEFAULT,         // ウィンドウを表示する位置(Y座標)
		CW_USEDEFAULT,         // ウィンドウの幅
		CW_USEDEFAULT,         // ウィンドウの高さ
		NULL,                  // 親ウィンドウのウィンドウハンドル
		NULL,                  // メニューハンドル
		hInst,                 // インスタンスハンドル
		NULL                   // その他の作成データ
	);
	if( hWnd == NULL ){ return 1; }

	// ウィンドウを表示する
	ShowWindow( hWnd, SW_SHOW );
	UpdateWindow( hWnd );

	// メッセージループ
	while( 1 )
	{
		BOOL ret = GetMessage( &msg, NULL, 0, 0 );  // メッセージを取得する
		if( ret == 0 || ret == -1 )
		{
			// アプリケーションを終了させるメッセージが来ていたら、
			// あるいは GetMessage() が失敗したら( -1 が返されたら )、ループを抜ける
			break;
		}
		else
		{
			// メッセージを処理する
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
	}

	return 0;
}

// ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch( msg )
	{
	case WM_DESTROY:  // ウィンドウを破棄するとき
		PostQuitMessage( 0 );
		return 0;
	}

	// 他のメッセージは、デフォルトの処理を行う
	return DefWindowProc( hWnd, msg, wp, lp );
}

少し長いですね。このプログラムを実行すると、ウィンドウが画面に表示されます。ソースも長いし、説明も長くなりますが、我慢して下さい。最初から全てを理解する必要は全くありません。

ウィンドウを表示させる必要がある以上、上のプログラムの大部分は必ず必要になります。そこで、もう少し再利用しやすい形で作成しておくと、それを毎回使いまわすことができます。細かい設定を変えるときに、該当する部分だけを修正すれば済む訳です。

さて、具体的に上のプログラムの各行が何をしているのかを説明してもいいのですが、今の時点で全てを覚えてもほとんど意味がありません。というより、それだけの労力に見合ったものは得られないでしょう。今後、様々なAPIを見ていく訳ですが、実はAPI関数は1000個以上あります。そんな膨大な量の関数群を暗記するのは困難です(しかも、C言語の標準関数とは違い、引数の数も非常に多いものがあります。中には引数が10個以上ある関数も存在します)。 Windowsプログラミングをしていく過程で、必要となった機能を、必要なときに調べるのが基本です。

とは言え、プログラムだけ見せて終わりという訳にもいかないので、簡単に説明します。ウィンドウを表示させるための流れは、

   1. ウィンドウクラスを用意する
   2. 用意したウィンドウクラスを登録する
   3. ウィンドウを作成する
   4. ウィンドウを表示する
   5. ウィンドウメッセージを処理する

といった感じです。ウィンドウクラスというのは簡単にいえば、ウィンドウの基本情報をまとめたものです。当分の間は、ウィンドウは1つしか作成しませんが、複数のウィンドウがあれば、それぞれが異なったウィンドウクラスを持つことになります。ちなみに、C++のクラス構造とは関係ありません。

ウィンドウクラスは、WNDCLASSEX構造体を使って表します。この構造体の変数を作り、そのメンバに必要な情報を格納し、それをRegisterClassEx()に渡します。そうすることで、ウィンドウクラスの登録が完了します。登録しないと、そのウィンドウクラスの情報を元に、ウィンドウを作成することができません。WNDCLASSEX構造体の各メンバの意味は、今後少しずつ明らかにしていきます。

ウィンドウの作成は、CreateWindow()で行います。第1引数に指定するウィンドウクラス名は、RegisterClassEx()で登録したWNDCLASSEX構造体のlpszClassNameに指定したものと一致させます。つまり、CreateWindow()の第1引数で指定したウィンドウクラス名をもつウィンドウクラスの情報を使って、ウィンドウが作成されます。

CreateWindow()は、戻り値としてHWND型の値を返します。これは ウィンドウハンドルと呼ばれるもので、作成したウィンドウに対して、何らかの処理・操作を加えたいときに必要になります。Windowsプログラミングの中では、○○○ハンドルというものがよく登場しますが、ハンドルというのは車のハンドルをイメージしてもらえばいいでしょう。車を操作するためにはハンドルが必要なのと同じで、ウィンドウを操作するにはウィンドウハンドルが必要なのです。このような意味があるため、CreateWindow()の戻り値は、必ず受け取っておき、どこかに保存しておきます。

CreateWindow()は、引数やウィンドウクラスの情報に間違いがあると失敗します。失敗した場合には、戻り値でNULLを返します(ハンドルの正体は、実際にはただのポインタです。だから失敗時はNULLで表現されます)。上のプログラムは、全て正しい値を指定しているので失敗しませんが、一応、CreateWindow()を呼び出した直後で、返されたウィンドウハンドルの中身を調べておくべきです。
○ウィンドウメッセージの処理

Windowsでは、何らかの操作が行われたとき、何が行われたかをメッセージ によってアプリケーションに伝えることになっています。プログラム的には、メッセージが発生したことを検知して、メッセージの種類に応じた適切な処理を実行しなければなりません。ここでいうメッセージには、例えば、ウィンドウの×ボタンが押された、マウスカーソルが移動した、クリックした、ウィンドウを移動させたといった非常に様々なものがあります。

メッセージが発生すると、そのメッセージの情報がメッセージキューと呼ばれる場所に格納されます。キュー(待ち行列)についてはアルゴリズムとデータ構造編第9章 で説明していますが、アルゴリズム的な意味合いを知っている必要は現状ではあまりありません。とにかく、そういう場所に格納されていきます。そしてプログラマは、定期的にメッセージキューを調べて、そこに溜まっているメッセージを取り出し、適切に処理していきます。

メッセージキューからメッセージを取り出すには、GetMessage()を使います。そして、メッセージを処理させるには、TranslateMessage()と DispatchMessage()を使います。

さて、各メッセージに応じた処理を、どこに記述すればいいかといえば、最初のプログラム例でいうと、 WndProc()という関数内になります。この関数は、ウィンドウクラスを用意するとき、WNDCLASSEX構造体のlpfnWndProc で指定しています。"このウィンドウクラス内で発生したメッセージは、WndProc()で処理する"ということです。 WndProc()のように、メッセージを処理するための関数を、ウィンドウプロシージャ といいます。ウィンドウプロシージャは必ず、

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

という形式にします。見慣れない文字が多いと思いますが、今は分からなくて構いません。

WndProc()内では、WM_DESTROYというメッセージの処理をしています。このメッセージは、ウィンドウが破棄されるときに発生します。通常、ウィンドウを閉じたら、そのアプリケーション自体も終了させるので、そのための処理を行っているのです。アプリケーションを終了させるには、 PostQuitMessage()を使います。

では、WM_DESTROY以外のメッセージはどうするのでしょう? 何もしなくていいという訳ではありません。作成するアプリケーションにとって、興味の対象とならないメッセージは、全てDefWindowProc() という関数に渡して、代わりに処理してもらうようにします。この関数は、 デフォルトウィンドウプロシージャと呼ばれています。つまり、メッセージの種類に応じて、デフォルトの処理を自動的に行ってくれます。デフォルトの処理を行わないと問題が起こります。例えば、マウスカーソルの移動に関するメッセージが、特にそのアプリケーションの固有の処理と関係がないからといって、何も処理しないと、いくらマウスを動かしても、カーソルは全く動かなくなってしまいます。


長くなりましたが、基本的な流れは最初のプログラムで完成しています。次のようなことを実際に試してみて、理解を深めて下さい。

    * 最初のプログラムを、自分で入力してみる。
    * 再利用しやすいように、関数化してみる。
    * ウィンドウの作成が失敗したときに、メッセージボックスを出して、エラーが発生したことを伝えるように改造してみる。


Win32API(C言語)編のトップページに戻る

サイトのトップページに戻る
1
目安箱バナー