第六回・ウィンドウを作る

さて、なんだかとっても久しぶりのような気がする第六回(汗)
今回はウィンドウを作るんでしたね。

説明はあとにしてとりあえずソースコードを見てみましょう。

え?久しぶりだからプロジェクトの作り方を忘れた?そんな馬鹿な。

  1. VC++を起動する
  2. メニューから「ファイル」→「新規作成」を選ぶ
  3. 「プロジェクト」タブの「Win32 Application」を選ぶ
  4. プロジェクト名に「succ06」と入力
  5. 「OK」をクリック
  6. (ver.6の場合のみ)ウィザードが起動するので「空のプロジェクト」をチェック→「終了」→「OK」
  7. メニューから「ファイル」→「新規作成」を選ぶ
  8. 「ファイル」タブの「C++ソースファイル」を選ぶ
  9. ファイル名に「succ06.cpp」と入力、「OK」をクリック
これで準備完了です。
え、前と違う?気にしてはいけません(--;)
#include <windows.h>

//プロトタイプ宣言
HWND InitWindow(void);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam);

//グローバル変数
HINSTANCE g_hInstance;

/////////////////////////////////////////////////////////////////////////////
//WinMain関数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                LPSTR lpszCmd, int nCmdShow)
{
        MSG msg;

        g_hInstance = hInstance;
        InitWindow();
 
        //メッセージループ
        while (GetMessage (&msg, NULL, 0, 0))
        {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
        }
        return msg.wParam;
}

/////////////////////////////////////////////////////////////////////////////
//ウィンドウを作る関数
HWND InitWindow(void)
{
        HWND hWnd;
        LPCTSTR szclassName = "succ";
        WNDCLASSEX wcex;

        ZeroMemory((LPVOID)&wcex, sizeof(WNDCLASSEX));

        //ウィンドウクラスを登録
        wcex.cbSize             = sizeof(WNDCLASSEX);
        wcex.style              = 0;
        wcex.lpfnWndProc        = WndProc;
        wcex.cbClsExtra         = 0;
        wcex.cbWndExtra         = 0;
        wcex.hInstance          = g_hInstance;
        wcex.hIcon              = NULL;
        wcex.hCursor            = LoadCursor(NULL,IDC_ARROW);
        wcex.hbrBackground      = (HBRUSH)(COLOR_WINDOW+1);
        wcex.lpszMenuName       = NULL;
        wcex.lpszClassName      = szclassName;
        wcex.hIconSm            = NULL;
        RegisterClassEx(&wcex);

        //ウィンドウ作成
        hWnd=CreateWindowEx(0,szclassName,"ウィンドウのタイトル",WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,CW_USEDEFAULT,
                CW_USEDEFAULT,CW_USEDEFAULT,
                NULL,NULL,g_hInstance,NULL);

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

        return hWnd;
}

/////////////////////////////////////////////////////////////////////////////
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                                                 WPARAM wParam, LPARAM lParam) 
{
        switch (message)
        {
                case WM_CLOSE:
                        DestroyWindow(hwnd);
                        return 0;
                case WM_DESTROY:
                        PostQuitMessage(0);
                        break;
                default:
                        return (DefWindowProc(hwnd,message,wParam,lParam));
        }
        return 0L;
}
ソースコード(コピー&ペースト用)

と、まぁこのソースをいつものようにコピー&ペーストしたら実行してみましょう。
え、実行の仕方も忘れた?
メニューの「ビルド」→「実行」です。
または赤いエクスクラメーションマーク(!)のアイコンをクリックでもOKです。

ウィンドウが作れましたね?作れたら大成功です。
このウィンドウは何もできませんが移動やサイズ変更などの基本的な動作は
できます。もちろん最大化、最小化、閉じることも可能です。
思ったより簡単でしたね。(本当か?)
では、細かい解説を・・・。

まず

	#include <windows.h>

はもう説明の必要はありませんね。ヘッダファイルをインクルードして
WindowsのAPIを使えるようにしています。

	//プロトタイプ宣言
	HWND InitWindow(void);
	LRESULT CALLBACK WndProc(HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam);

このプロトタイプ宣言というのは「後でこういう関数が出てくるからね」と
いう宣言です。今回のようなプログラムだとInitWindow()関数が登場する前に
InitWindow()を呼び出しています。なのでこのプロトタイプ宣言をしておかないと
「そんな関数あるかー!」と、怒られてしまうわけです。
ちなみにwindows.h等のヘッダファイルの中身の多くはこのプロトタイプ宣言です。

	//グローバル変数
	HINSTANCE g_hInstance;

グローバル変数というのは今回のプログラムのように複数の関数がある場合、
どの関数からでもアクセスできる変数です。ちなみに関数の中で宣言されている
変数はローカル変数と言い、その関数内でしか使用することができません。
今回のg_hInstanceという変数はこのプログラムのインスタンスハンドルというものを
保存するのに使います。

	/////////////////////////////////////////////////////////////////////////////
	//WinMain関数
	int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
					LPSTR lpszCmd, int nCmdShow)
	{
		MSG msg;
	
		g_hInstance = hInstance;
		InitWindow();
	 
		//メッセージループ
		while (GetMessage (&msg, NULL, 0, 0))
		{
			TranslateMessage (&msg);
			DispatchMessage (&msg);
		}
		return msg.wParam;
	}

今までにも何度も出てきたWinMain関数、Windowsのプログラムはこの関数から
スタートします。
第一引数にインスタンスハンドルがあるのでこれをさっきのg_hInstanceに
保存しています。そしてその後InitWindow()関数を呼び出します。
この関数については後で説明します。
そしてその後、メッセージループに入り、延々とループします。

        /////////////////////////////////////////////////////////////////////////////
        //ウィンドウを作る関数
        HWND InitWindow(void)
        {
                HWND hWnd;
                LPCTSTR szclassName = "succ";
                WNDCLASSEX wcex;
        
                ZeroMemory((LPVOID)&wcex, sizeof(WNDCLASSEX));
        
                //ウィンドウクラスを登録
                wcex.cbSize                     = sizeof(WNDCLASSEX);
                wcex.style                      = 0;
                wcex.lpfnWndProc        = WndProc;
                wcex.cbClsExtra         = 0;
                wcex.cbWndExtra         = 0;
                wcex.hInstance          = g_hInstance;
                wcex.hIcon                      = NULL;
                wcex.hCursor            = LoadCursor(NULL,IDC_ARROW);
                wcex.hbrBackground      = ( HBRUSH)( COLOR_WINDOW+1);
                wcex.lpszMenuName       = NULL;
                wcex.lpszClassName      = szclassName;
                wcex.hIconSm            = NULL;
                RegisterClassEx(&wcex);

                //ウィンドウ作成
                hWnd=CreateWindowEx(0,szclassName,"ウィンドウのタイトル",WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT,CW_USEDEFAULT,
                        CW_USEDEFAULT,CW_USEDEFAULT,
                        NULL,NULL,g_hInstance,NULL);
        
                //ウィンドウ表示
                ShowWindow(hWnd, SW_SHOW);
                UpdateWindow(hWnd);
        
                return hWnd;
        }

そしてこれが先ほどのWinMain関数で呼び出されていたInitWindow()関数です。
この辺はほとんどお約束なので余り詳しく解説しません。
簡単に説明するとウィンドウクラス構造体の各メンバにウィンドウ作成に必要な
データを入れて、RegisterClassEx()関数でそのウィンドウクラスを登録します。
登録が終わったらCreateWindowEx()関数でウィンドウを作成し、ShowWindow()、
UpdateWindow()でウィンドウを表示します。
さっきのg_hInstanceがここで使われていますね?

	/////////////////////////////////////////////////////////////////////////////
	//ウィンドウプロシージャ
	LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
	                                                 WPARAM wParam, LPARAM lParam) 
	{
	        switch (message)
	        {
	                case WM_CLOSE:
	                        DestroyWindow(hwnd);
	                        return 0;
	                case WM_DESTROY:
	                        PostQuitMessage(0);
	                        break;
	                default:
	                        return (DefWindowProc(hwnd,message,wParam,lParam));
	        }
	        return 0L;
	}

そしてこれがウィンドウプロシージャです。どこから呼ばれているかわかりますか?
どこからも呼ばれてない?
はい、そうです。どこからも呼ばれてません。
じゃあいつ呼ばれるのか。実はこの関数はウィンドウにメッセージがくる度に
呼び出されます。なのでその度にswitch構文でどのメッセージか見分けて
処理が必要なメッセージならば処理をする・・・・というわけです。
ちなみに処理が必要ないメッセージは全てDefWindowProc()関数に任せておけば
適当に処理してくれます。

このウィンドウプロシージャは一つのウィンドウに一つ必ず必要で、ウィンドウクラス
を登録する段階でウィンドウクラス構造体のlpfnWndProcメンバにそのアドレスを
入れておかなければなりません。

さて、やっとまともなWindowsプログラムが作成できましたね。
Windowsではウィンドウを作るだけでこれだけのコーディングが必要になります。
VBなどのRADツールが必要になるわけですね。MFC等のクラスライブラリも
コーディングの手間を省くために開発されたんだと思います。一通りSDKでの
プログラミングを身につけたら他の環境に移るのも良いかもしれません。

今回は関数が複数あったり構造体が出てきたりといろいろ大変でしたね。
よくわからなかった人は参考書と格闘して理解してください。
では、お疲れさまでした。

戻る