Windowの表示 - yoshimune/LearningDirectX11 GitHub Wiki

Sammury

VC++ Win32アプリケーション開発において、単純なWindowを表示するまでを解説します。
本プロジェクトはDirectX11開発方法の習得を目的としています。しかし、DirectXはWin32アプリケーション上で動作するものであり、最低限のWin32アプリケーション開発の理解は必須です。最低限とは「アプリケーションを開始、ウインドウの表示、アプリケーションの終了」と定義します。つまり、これだけのことを実現する方法を示します。

Contents

  • 01_01 Windowクラスを作成・登録
  • 01_02 Windowインスタンスを作成・表示
  • 01_03 Messageループ(ウインドウプロシージャ)
  • 完全なコード
  • 参考

01_01 Windowクラスを作成・登録

ウインドウを表示するために、まずウィンドウクラスを作成します。

プログラムのエントリポイント

エントリポイントは WinMainwWinMain です。両者の違いは第3引数の型です。今回はwWinMainを使います。第2引数と第3引数は今回使わないので、UNREFERENCED_PARAMETERマクロを使ってWarningが出ないようにしておきます。(第2引数はどのアプリケーションでも使わないらしい…)

// エントリポイント
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
	// 引数未使用時のWarning対策
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(pCmdLine);
...
}

ウィンドウクラスの作成・登録

全てのウィンドウはウィンドウクラスと関連付けられていないといけません。ここでいうウィンドウクラスとは、C++でいうところのクラスとは異なることに注意してください。ウィンドウクラスはOSによって内部的に使用されるデータ構造です。ウィンドウクラスは作成後、システムに登録する比強があります。

今回の例ではウィンドウは一つだけなので、一つのウィンドウクラスを作成して登録します。WNDCLASSEX構造体を用意してメンバーを初期化します。

// ウィンドウクラス名を用意
const wchar_t CLASS_NAME[] = L"01 SimpleWindow";

// Windowクラスの作成
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = CLASS_NAME;
wcex.hIconSm = NULL;

ここで注意点ですが、WNDCLASSEXのメンバー初期化処理のうち、↓のものに注目してください。

wcex.lpfnWndProc = WindowProc;

WindowProcウィンドウプロシージャと呼ばれる関数です。ウィンドウプロシージャについては後述します。WinsowProcを代入するため、wWinMainメソッドより前にWindowProcの宣言をしておきます。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

次にウィンドウクラスを登録します。

// ウィンドウクラスを登録
if (!RegisterClassEx(&wcex))
{
	// 登録に失敗した場合
	DWORD dwError = GetLastError();
	if (dwError != ERROR_CLASS_ALREADY_EXISTS)
	{
		return HRESULT_FROM_WIN32(dwError);
	}
}

01_02 Windowインスタンスを作成・表示

ウィンドウインスタンスの作成

ウィンドウインスタンスを作成します。ウィンドウインスタンス作成のため、CreateWindowEx関数を呼びます。また、ウィンドウのサイズを指定するため、RECTを用意します。

// ウィンドウインスタンスの作成 =========================================

// ウインドウクラスのクライアント領域(=DirectXの描画領域)を指定
RECT rc = { 0, 0, 640, 480 };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);

// HWND はウィンドウのハンドラ
HWND hwnd = CreateWindowEx(
	0,					// Optional window styles.
	CLASS_NAME,				// Window class
	L"01 Simple Window",			// Window text
	WS_OVERLAPPEDWINDOW,			// Window style

	// Size and position
	CW_USEDEFAULT, CW_USEDEFAULT, (rc.right - rc.left), (rc.bottom - rc.top),

	NULL,		// Parent Window
	NULL,		// Menu
	hInstance,	// Instance handle
	NULL		// Additional application data
);

// 作成失敗
if (hwnd == NULL)
{
	DWORD dwError = GetLastError();
	return HRESULT_FROM_WIN32(dwError);
}
// ======================================================================

ウィンドウの表示

ウィンドウはShowWindow関数を呼び出して表示します。

// ウィンドウの表示
ShowWindow(hwnd, nCmdShow);

01_03 Messageループ(ウインドウプロシージャ)

メッセージとは

GUIアプリケーションは、ユーザー入力やOSからの通知といったイベントを常に受け取ることで状態を更新しています。Windowsでは Message-passing モデルによってこれを実現しています。アプリケーションとOSはメッセージをやり取りすることによって相互に干渉します。

ウィンドウを作成するスレッドごとに、OSはウィンドウメッセージキューを作成します。このキューにメッセージを保持します。キューはアプリケーションプログラムからは隠蔽されています。

メッセージループ

アプリケーションは、実行中に絶えず送られてくるメッセージを受け取り、適切に処理しなくてはなりません。以下のような処理になるでしょう。

  1. メッセージキューからメッセージを取り出す
  2. メッセージの内容に合わせて処理を施す

通常はwhileループを使って常にメッセージキューの監視と処理ハンドリングを行います。

// メッセージループ =====================================================
bool bGotMsg;
MSG msg;
msg.message = WM_NULL;

// メッセージをキューから取り出す
PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);

// WM_QUITメッセージが来るまでループする
// メッセージが無い ≠ WM_QUITではない
// ウィンドウプロシージャで PostQuitMessage(0); が実行された後にWM_QUITが送られてくる
while (WM_QUIT != msg.message)
{
	// メッセージをキューから取り出す
	bGotMsg = (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0);

	if (bGotMsg)
	{
		// キーボード入力を扱う
		TranslateMessage(&msg);

		// OSにウィンドウプロシージャを呼び出すよう通知する
		DispatchMessage(&msg);
	}
	else
	{
		// メッセージがない場合
		// 今回は何もしていないが、ゲームなどの場合は、ここでUpdateとRenderを行う
	}
}
// ======================================================================
  • メッセージはMSG構造体の形式で受け取ります。
  • PeekMessageはメッセージキューからメッセージを取り出すメソッドです。
  • メッセージの種類はMSG.message(UINT)で判断します。
    • VM_QUITはアプリケーションが終了したことを伝えるメッセージです。このメッセージが来たらループを終了します。
  • TranslateMessageはキーボード入力からのメッセージをハンドリングします。
  • DispatchMessageはウィンドウプロシージャ(後述)を呼び出すようOSに通知します。
  • メッセージキューになにもメッセージがない場合もあります。通常のゲームでは、メッセージがない場合にゲーム状態の更新と再描画を行います。(今回の例では記述していません)

ウィンドウプロシージャ

ウィンドウプロシージャは、キューから取り出したメッセージを参考にアプリケーションの挙動を決定するメソッドです。実装は単純にswitch文で処理を振り分けているだけです。

// ウィンドウプロシージャ
// DispatchMessage が呼び出されたあと、OSから呼ばれる
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	// アプリケーション終了時(ウィンドウが消えた後)
	case WM_DESTROY:
		// メッセージ送信を終了してもらう
		PostQuitMessage(0);
		return 0;

	// その他のメッセージ
	default:
		// デフォルトの対応をしてもらう
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
}

今回の例では「アプリケーション終了時(ウィンドウが消えた後)」と「デフォルトの対応」しか定義していません。

  • PostQuitMessageはスレッドが自身を終了させるようシステム要求します。要するに終了したいときに呼びだします。
  • DefWindowProcはメッセージに対して既定の処理を提供します。つまり、特別に処理を設計する必要のないメッセージに対してはこのメソッドを使って既定の処理を施します。

完全なコード

完全なコードは↓にあります。 https://github.com/yoshimune/LearningDirectX11/tree/master/01_SimpleWindow/01_SimpleWindow

参考

Creating a Window
Window Messages
Writing the Window Procedure
WinMain: The Application Entry Point