【C++】WindowsAPIでウィンドウを表示

2019年2月27日

本稿は、C++(WindowsAPI)でウィンドウの作成を試したいと思います。使いそうな部分を中心にやっていきます。



1.ウィンドウを表示、ウィンドウタイトル指定

まずはウィンドウを表示するための処理です。

ウィンドウを表示するためにはwindows.hをインクルードし、CreateWindow関数を使用します。

CreateWindow("STATIC"//クラス名
		, "WinAPITest"//ウィンドウタイトル
		, WS_CAPTION,//ウィンドウスタイル
		0, 0,//ウィンドウ座標
		400, 300,//windowsサイズ
		NULL, NULL, hInstance, NULL
);

ここでは、ウィンドウタイトル、ウィンドウ座標、ウィンドウサイズの指定だけわかれば問題なさげです。

ウィンドウスタイルは基本的に固定の指定を使うので特に気にしません。ポップアップウィンドウなどを作成したい場合は勉強しておくと良いです。

実際に実行できるコードは下記です。

#include <windows.h>
#include <thread>
#include <chrono>

int WINAPI WinMain(
	HINSTANCE hInstance,      // 現在のインスタンスのハンドル
	HINSTANCE hPrevInstance,  // 以前のインスタンスのハンドル
	LPSTR lpCmdLine,          // コマンドライン
	int nCmdShow              // 表示状態
	) {

	//ウィンドウ作成
	HWND hWnd = CreateWindow("STATIC", "WinAPITest", WS_CAPTION,
		0, 0,//ウィンドウ座標
		400, 300,//windowsサイズ
		NULL, NULL, hInstance, NULL);
	if (hWnd == NULL) return 0;

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

	//5秒スリープ
	std::this_thread::sleep_for(std::chrono::seconds(5));
}
実行結果

5秒スリープしているのは、メインメソッドが終了するとアプリケーションが終了しますので、それを防ぐためです。

2.ウィンドウの詳細設定、メッセージループ、閉じるボタンでアプリケーションの終了

さて、1で説明したプログラムは普通のアプリケーションみたいにウィンドウの操作ができませんし、すぐに終了してしまいます。

なので一般的なアプリケーションで使われているような形式にしてしまいます。

あとはゲーム制作に必要になりそうなアイコン指定なども試したいので、WNDCLASSEXを使用した方法でやってみます。

WNDCLASSEX構造体は作成した後に詳細設定を行い、RegisterClassEx関数で登録します。

そのあとCreateWindow関数を呼び出しますが、一つ目の引数と構造体のlpszClassNameの値を合わせておく必要があります。


	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 = hInstance;
	wcex.hIcon = NULL;//アイコン指定
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);//ウィンドウ上のカーソル
	wcex.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);//ウィンドウ背景色
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = "クラス名";//クラス名は合わせといて
	wcex.hIconSm = NULL;
	RegisterClassEx(&wcex);

	hWnd = CreateWindow("クラス名", "ウィンドウタイトル"
		, WS_OVERLAPPEDWINDOW,//閉じるボタンとか出す指定
		0, 0,//ウィンドウ座標
		400, 300,//ウィンドウサイズ
		NULL, NULL, hInstance, NULL
	);

早々に長いコードになってしまいましたが、重要なとこ以外は無視でいいです。コメントアウトで内容書いてるとこくらいは覚えといたらいいかなと思います。

次にメッセージループとウィンドウプロシージャの関数についてです。

ここで言うメッセージは、ウィンドウの操作をした場合などに発生します。例えばフォーカスを当てたとき、ウィンドウを移動したとき、ウィンドウをクリックしたときなどですね。

メッセージの取得はGetMessage関数で可能です。

GetMessage(メッセージを取得するためのポインタ, NULL, 0, 0)

この関数はウィンドウメッセージを取得するまでスレッドを待機します。メッセージが取得できた場合でアプリケーションの終了メッセージを取得した場合は0(false)を返し、それ以外は1(true)を返します。

これは関数の戻り値をループ等の条件に指定できそうですね。

このフラグを利用してメッセージループを作ってみます。

ループ内ではDispatchMessage関数を呼び出します。この関数の実行でウィンドウプロシージャに指定した関数が呼び出されます。

	//メッセージループ
	MSG msg = {};
	while (GetMessage(&msg, NULL, 0, 0) == 1) {
		TranslateMessage(&msg);//メッセージ変換
		DispatchMessage(&msg);//プロシージャへコールバック
	}

メッセージ取得時にスレッドを待機せずに別の処理をしたい場合はPeekMessage関数が使用できます。こちらはゲームループの記事で使ってみることにします。

メッセージを取得した後はウィンドウプロシージャの出番です。

上記のWNDCLASSEXを設定するコードで

wcex.lpfnWndProc = WndProc;//ウィンドウのメッセージを処理するためのコールバック関数

がありましたが、ここでウィンドウプロシージャ関数を登録しています。

この呼び出し先の関数でウィンドウの操作時の特殊処理を実装できます。

ウィンドウが消えたときにアプリケーション終了メッセージを要求する場合はWM_DESTROYイベント発生時にPostQuitMessage(0);を指定しましょう。PostQuitMessage(0)はメッセージループの戻り値が0(false)のメッセージを渡すことができますので、メッセージループを終了させるのに使えます。

特殊処理が不要な場合はDefWindowProc関数を呼び出しておくと、デフォルトの処理を実行してくれます。

LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
	WPARAM wParam, LPARAM lParam) {
	switch (message) {
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

ウィンドウ表示サンプル

それではゲーム制作に使用する最低限の機能を実装したウィンドウ表示サンプルを示します。

ウィンドウのアイコンはこれをサンプルに使用しました。exeファイルと同じフォルダに配置するとOK。
test.ico

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
	WPARAM wParam, LPARAM lParam) {
	switch (message) {
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}
int WINAPI WinMain(
	HINSTANCE hInstance,      // 現在のインスタンスのハンドル
	HINSTANCE hPrevInstance,  // 以前のインスタンスのハンドル
	LPSTR lpCmdLine,          // コマンドライン
	int nCmdShow              // 表示状態
	) {
	HWND hWnd;
	LPCTSTR szclassName = "WinAPITest";
	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 = hInstance;
	wcex.hIcon = (HICON)LoadImage(          // アイコン
		NULL, "test.ico", IMAGE_ICON,
		0, 0, LR_SHARED | LR_LOADFROMFILE
		);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szclassName;
	wcex.hIconSm = NULL;
	RegisterClassEx(&wcex);


	//ウィンドウ作成
	hWnd = CreateWindow(szclassName, "Title", WS_OVERLAPPEDWINDOW,
		0, 0,
		400, 300,
		NULL, NULL, hInstance, NULL);


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

	//メッセージループ
	MSG msg = {};
	while (GetMessage(&msg, NULL, 0, 0) == 1) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}
実行結果

ウィンドウ表示するだけでここまでコードを書かなきゃいけないのは本当に面倒。
Javaなら数行で済むはずなのに・・・
.NETならコードすら書かなくてもいいのに・・・

今回はウィンドウを作成しましたが、そこまで使いそうにない機能に関しては端折りました。もっと詳しく知りたいという方はこちらのサイトなどで勉強してみてはいかがでしょうか?WindowsAPIに関連する記事がたくさんあり、とても参考になります。

C++

Posted by nompor