WindowsAPI

■WindowsAPIの必要事前知識

●ハンドル:
	Windowsはプログラムやウィンドウ、ファイルなどを
	識別するためにユニーク(一意)な番号を設定しています。
	この番号はWindowsが管理しており、その番号を指定して
	ウィンドウやファイルを操作しています。
	このユニークな番号のことをハンドルと呼んでいます。
	ハンドルは識別するデータの種類ごとに「~ハンドル」と呼ばれており、
	ウィンドウに対して設定しているハンドルと「ウィンドウハンドル」、
	ファイルに対して設定してるハンドルを「ファイルハンドル」と呼んでいます。
	directx_001

●インスタンス:
	インスタンスとは「実体」という意味で、オブジェクト指向においてよく使われる用語です。
	メモリ上に領域が確保されたデータをインスタンスと呼び、
	クラスを作成する場合、クラスのデータがメモリ上に確保されることから
	インスタンス化と呼んでいます。

	インスタンスハンドル:
		インスタンスハンドルとはOS(Windows)がアプリに設定したハンドルのことです。
		この後に説明するWinMain関数でインスタンスハンドルが引数として渡されていますが、
		それがOS(Windows)が決めたそのアプリのハンドル(番号)になります。

●WindowsAPI:
	WindowsAPIとはWindowsプログラミングを行うためにMicrosoftが提供しているAPIのことです。
	APIとはApplication Programming Interfaces の略で
	プログラムを行うために使用できる関数や規約の集合の事です。
	WindowsAPIを使用することでWindowsアプリを作成するために必要な機能を
	自分で作成せずに済むので作業工数を短縮することができます。

		例:
			ウィンドウを作りたい => CreateWindow関数を使用すれば簡単に作れる

●WinMain関数:
	WinMain関数はC言語のMain関数にあたる関数です。
	Windowsアプリを作成する場合は必ずWinMainからプログラムを始める必要があります。

		WinMain:
			int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

			内容:
				Windowsアプリを作る場合のエントリポイント
				C/C++言語のmain関数の役割を持つ

			戻り値:
				int:
					WinMain関数の実行結果を返す
					メッセージループ開始前は0をメッセージループ開始後は
					メッセージ.wParamの値を返す
		
			関数名:
				WinAPI WinMain
					WinMain関数の前に設定されている WinAPI はWindows関数を
					呼ぶために必要な規約
					ほとんどのWINAPIの関数はWinMainのようにWinAPIがある

			引数:
				第一引数:
					HINSTANCE
						このプログラム(アプリ)に割り当てられた
						インスタンスハンドル

				第二引数:
					HINSTANCE
						古い仕様の残り
						常にNULLが入っているので無視しても問題がない

				第三引数:
					LPSTR
						コマンドラインで設定された文字列のポインタ
						C言語のmain関数の「char *argc[]」にあたる内容

				第四引数:
					int
						ウィンドウの初期表示状態の指定
						通常表示の他に最大化や最小化表示などがある

■ウィンドウ作成の流れ

ウィンドウを作成する手順は以下の通りです。
	1.ウィンドウ情報の登録

		↓

	2.ウィンドウ作成

		↓

	3.ウィンドウのリサイズ

■1.ウィンドウ情報の登録

WindowsAPIを使用してウィンドウを表示する場合、CreateWindow関数を
使用してウィンドウの作成を行います。
ですが、CreateWindowの前にウィンドウの性質を設定する必要があります。
性質とはウィンドウスタイル(ウィンドウサイズが変更された時に
再描画を行う、閉じるボタン無効化等)やタイトルのアイコン、
アプリのアイコン、カーソル、背景色の設定などがあります。

●WNDCLASSEX構造体
	ウィンドウ情報を設定するための構造体です。
	構造体のメンバ変数は以下の内容になります。

	・UINT cbSize
		WNDCLASSEX構造体のサイズ
	
	・UINT style
		ウィンドウスタイル(CS_HREDRAWやCS_VREDRAWなど)
	
	・WNDPROC lpfWndProc
		ウィンドウプロシージャのアドレス
	
	・int cbClsExtra
		予備メモリ(構造体登録時に確保される)
		基本的に0
	
	・int cbWndExtra
		ウィンドウオブジェクト作成時に確保されるメモリサイズ
		基本的に0
		
	・HINSTANCE hInstance
		インスタンスハンドル
		
	・HICON hIcon
		アプリのショートカットなどで使用されるアイコン
		デフォルトでいいならNULL
		
	・HCURSOR hCursor
		ウィンドウのクライアント上のマウスカーソル
		デフォルトでいいならNULL
	
	・HBRUSH hbrBackground
		ウィンドウのクライアント領域の背景色
	
	・LPCTSTR lpszMenuName
		メニュー名
		メニューがなければNULL
	
	・LPCTSTR lpszClassName
		Windowクラスの名前
	
	・HICON hIconSm
		タイトルバーで使用されるアイコン
		デフォルトでいいならNULL

●RegisterClassEx
	内容:
		WNDCLASSEXを登録するための関数
		ここで登録した情報が後に行うCreateWindow関数に反映される

	戻り値:
		成功:ATOM型の値
		失敗:0

	引数:
		WNDCLASSEXのポインタ

	※戻り値のATOMは特に覚えなくていいので、0になったら登録が失敗
	 それ以外なら成功と覚えておけば問題はありません。

■2.ウィンドウ作成

ウィンドウクラスの登録を行ったら次はウィンドウの作成を行います。
作成にはCreateWindow関数を使用します。

●CreateWindow関数
	内容:
		ウィンドウを作成する関数
		無事成功すると作成されたウィンドウのハンドルが返される

	戻り値:
		HWND:
			ウィンドウハンドル
	
	引数:
		LPCSTR:
			登録されているウィンドウクラスの文字列
	
		LPCSTR:
			ウィンドウ名(タイトル部分に表示される文字列)
	
		DWORD:
			ウィンドウスタイル
				メニューボックスの有無
				最大、最小ボタンの有無等
		
		int:
			ウィンドウの表示位置(X軸)
		
		int:
			ウィンドウの表示位置(Y軸)
		
		int:
			ウィンドウの横幅
		
		int:
			ウィンドウの縦幅
		
		HWND:
			親のウィンドウハンドル
			親のウィンドウがなければNULL
			
		HMENU:
			メニューハンドル
			メニューがなければNULL
			
		HINSTANCE:
			インスタンスハンドル
			
		LPVOID:
			WM_CREATEメッセージのlpparamのCREATESTRUCT構造体のポインタ
			NULLで問題ない

■3.ウィンドウのリサイズ

CreateWindowで作成したウィンドウのサイズはタイトル部分を含めたサイズになっています。
しかし、ゲームなどの場合はクライアント領域のサイズをCreateWindowで設定したサイズに
変更する必要があります。

directx_002

●リサイズの方法
	リサイズの方法はクライアント領域以外の部分を算出して
	その値をクライアント領域に加算します。

	1.全体のサイズ取得
		リサイズのための最初の手順として全体のサイズを取得します。
		ウィンドウサイズはGetWindowRect関数で取得できます。

	2.クライアントサイズの取得
		次はクライアント領域のサイズを取得します。
		クライアント領域のサイズはGetClientRect関数で取得できます。

	3.ウィンドウ部分のサイズの算出
		ウィンドウ部分のサイズを出すには全体のウィンドウサイズの幅と高さから
		クライアント領域の幅と高さを引いて割り出します。
		directx_003

	4.新規ウィンドウサイズの算出
		ウィンドウ部分のサイズが割り出せたら新規のウィンドウサイズを算出します。
		計算式は3で割り出したウィンドウ部分のサイズと本来クライアント領域にしたかった
		サイズを足します。
		directx_004

	5.新規サイズの登録
		新規サイズが割り出せたらSetWindowPos関数を使用して
		新規のサイズを登録します。

■メッセージ駆動型プログラミング(イベント駆動型プログラミング)

WindowsプログラムはOS(Windows)から様々なメッセージが送られてきます。
メッセージの内容は「画面内で右クリックされた」「ウィンドウのサイズを変更した」等です。
この送られてきたメッセージに対して処理を行うプログラムを
メッセージ駆動型プログラミングと呼びます。
「画面クリック」や「キーボードを押した」等のユーザーの行動(イベント)によって
メッセージが送信されることからイベント駆動型プログラミングとも呼ばれています。

■メッセージ

OS(Windows)から送られてくる情報のことをメッセージと呼びます。
メッセージの種類は「画面をクリックした」「画面を再描画する必要がある」
「画面を最小、最大化する」等、数多く用意されています。

●メッセージ取得
	OS(Windows)からメッセージを受信するにはGetMessageかPeekMessage関数を使用します。
	この二つの関数にはそれぞれ特徴があり、その特徴を利用して
	後述するメッセーループ、メインループを作成します。

	・GetMessage
		特性:
			OS(Windows)がメッセージを送信するまで処理が停止する
			※scanf関数のようにそれ以降に処理が進まない

		内容:
			OS(Window)からのメッセージを取得する

		戻り値:
			WM_QUITメッセージ:0
			WM_QUITメッセージ以外:非0

		引数:
			LPMSG lpMsg
				取得メッセージ
		
			HWND ウィンドウハンドル
				メッセージを取得するウィンドウハンドル
			
			UINT wMsgFilterMin
				取得メッセージの最小値
			
			UINT wMsgFilterMax
				取得メッセージの最大値
		
	・PeekMessage
		特性:
			OS(Windows)にメッセージの有無を確認しに行く
			GetMessageと違い、メッセージの有無に関係なく処理が進む

		内容:
			OS(Window)からのメッセージを取得する

		戻り値:
			WM_QUITメッセージ:0
			WM_QUITメッセージ以外:非0

		引数:
			LPMSG lpMsg
				取得メッセージ
		
			HWND ウィンドウハンドル
				メッセージを取得するウィンドウハンドル
		
			UINT wMsgFilterMin
				取得メッセージの最小値
	
			UINT wMsgFilterMax
				取得メッセージの最大値
		
			UINT wRemoveMsg
				取得メッセージの削除オプション
		
			PM_NOREMOVE:
					取得したメッセージをメッセージキューから削除しない
	
			PM_REMOVE:
					取得したメッセージをメッセージキューから削除する

●メッセージの変換
	メッセージの種類の中にキーボードのいずれかを押したというものがあります。
	これでキーボードが押された内容はわかりますが、
	大文字、小文字の判断はできません。
	そこで、キーボード入力メッセージに対して変換をかけて
	押されたキーボードの文字がわかるようにします。
	このメッセージの変換にはTranslateMessage関数を使用します。

	・TranslateMessage
		メッセージ変換関数はTranslateMessageを使用します。
		この関数を使用することでメッセージの変換が行われます。

●受信メッセージの送信
	受信したメッセージはウィンドウプロシージャと呼ばれる関数で
	処理しなければいけません。
	ウィンドウプロシージャは通常の関数と異なるので
	呼び出すためには専用の関数にメッセージを渡して
	ウィンドウプロシージャを呼び出します。
	このメッセージ送信にはDispatchMessage関数を使用します。

	・DispatchMessage
		メッセージをウィンドウプロシージャに送信する

■ウィンドウプロシージャ

ウィンドウプロシージャとはOS(Windows)から送られてきたメッセージを
処理するためのコールバック関数です。
この関数はウィンドウを作成する際に登録します。
ウィンドウプロシージャは戻り値や引数の型や数は決まっており、
変更することができません。
変更可能な箇所は関数名と関数内部の処理です。

●ウィンドウプロシージャの基本形
	LRESULT CALLBACK 関数名(HWND, UINT, WPARAM, LPARAM)

	戻り値:
		LRESULT:
			基本的に自分でメッセージを処理した場合は0、
			それ以外はDefWindowProcの戻り値を返す

	CALLBACK:
		WINAPIと同様の呼び出し規約

	引数:
		HWND:
			ウィンドウハンドル

		UINT:
			メッセージ

		WPARAM、LPARAM:
			メッセージに対する付加情報
			メッセージの内容で情報は異なる

●ウィンドウプロシージャ例:
	LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
	{
		switch (msg)
		{
		// キーを押した
		case WM_KEYDOWN:
			break;
		default:
			return DefWindowProc(hwnd, msg, wparam, lparam);
		}

		return 0;
	}

メッセージ取得からウィンドウプロシージャまでの流れ

	directx_009

■メッセージループ

メッセージループはWinMain関数に存在するループ処理です。
このループを抜けるとWinMain関数が終了し、プログラムが終了します。
つまり、このメッセージループがWinMainの核となる部分です。

●メッセージループの終了条件
	メッセージループの終了条件は二つあります。
	一つはOSから特定のメッセージを取得すること、
	もう一つはOSからのメッセージ取得関数が失敗した場合です。
	メッセージ取得関数は「GetMessage」です。
	この関数の戻り値が「0」の場合終了メッセージ取得、
	「-1」の場合が関数失敗になります。

	bool ret = false;
	MSG msg;
	// GetMessageの戻り値が0になるまでループする
	while ((ret = GetMessage(&msg, hWnd, 0, 0) != 0)
	{
		// -1は関数エラーなのでループを抜ける
		if (ret == -1)
		{
			break;
		}

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

●メッセージループの欠点
	メッセージループの欠点はメッセージが送られてきた時以外は
	待機し続けることです。
	GetMessageの仕様でOS(Windows)からメッセージが送られてくるまでは
	scanf関数のように処理が先に進まず待機し続けます。
	なのでゲームのような常に画面が切り替わり続けるような
	アプリではメッセージループは有効ではありません。

■メインループ

ゲームなどの一定の周期で画面を切り替えるアプリでは
メッセージループではなくメインループと呼ばれるループを作成し、
そのループが一定の周期で繰り返されるようにします。

例:
	bool game_loop = true;

	while (game_loop)
	{
		// 0.017秒でループする
	}

●OSからのメッセージに対する対処方法
	メインループを作成したら、その中でアプリのメイン処理を
	実装していくことになりますが、ほとんどのアプリがOS(Windows)の
	メッセージを使用することになりますので、
	メインループ内でメッセージを取得する必要があります。
		
	取得のために使用する関数がPeekMessageです。
	この関数はGetMessageと同様にOS(Windows)から
	メッセージを取得する関数ですが、GetMessageと違って
	メッセージがなくても待機状態にはならず、プログラムは進みます。
	
	例:
		bool game_loop = true;

		while (game_loop)
		{
			MSG msg;
	
			// メッセージ取得(0:メッセージ有り、1:メッセージ無し)
			if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != 0)
			{
				// メッセージ処理
			} else {
				// アプリ本編の処理
			}
		}

●フレーム
	フレームとはメインループでループする回数に使用する単位です。
	例:
		メインループを60回ループ => 60フレーム

	1フレームの単位時間はゲームによって異なりますが、
	ほとんどのゲームが1秒間を60フレームまたは30フレームとして開発しており、
	60フレームの場合は1フレームが17ms、30フレームの場合は33msとなります。

■FPS固定処理

ゲームではメインループ1回にかかる時間は固定する必要があります。
でないと時間経過などで正確な時間がはかれなくなったり、
PCのスペックや環境によってゲームの結果が変わってしまいます。
	
※今回紹介している方法は複数あるFPS固定処理のうちのひとつです。
 この方法だけが正解というわけではありません。

●時間取得
	時間取得はtimeGetTime関数を使用します。
	この関数は5msの精度で時間を計測することが可能です。
	ただ、ゲームではより高精度の時間計測が必要となるので、
	この後に説明するtimeBeginPeriod関数を使用して精度をあげます。

	・timeGetTime:
		内容:
			システムの時間(ミリ秒単位)を取得する

		戻り値:
			システムの時間(ミリ秒単位)
	
●取得時間の精度向上処理
	timeGetTimeの通常の精度ではゲームの1フレームの時間を
	正確に測ることはできません。
	正確に測るには取得する時間の精度を上げる必要があり、
	そのためにはtimeBeginPeriodとtimeEndPeriod関数を使用します。
	関数の使用場所はtimeBeginPeriodはメインループ開始前、
	timeEndPeriodはメインループ終了後です。

	・timeBeginPeriod
		内容:
			タイマーの精度を変更する

		戻り値:
			成功:TIMERR_NOERROR
			失敗:TIMERR_NOCANDO

		引数:
			タイマーの新しい精度(ms)

		例:
			timeBeginPeriod(1); // タイマーの精度を1msに変更する

	・timeEndPeriod
		内容:
			timeBeginPeriodで設定した内容を解除する

		引数:
			timeBeginPeriodで設定した値

		戻り値:
			成功:TIMERR_NOERROR
			失敗:TIMERR_NOCANDO

●精度が高くない理由
	通常のタイマーの精度が高性能ではない理由は
	タイマーの精度を高めれば高めるほど処理コストが増加するからです。
	なので、通常はタイマーの精度をゆるく設定してあり、
	ゲームなどの高精度タイマーが必要であれば、精度を高められるようになっています。

●FPS固定処理フロー
	directx_005
	上のフローにあるようにメインループ内で必要なゲームの処理を行った後、
	timeGetTimeで時間計測を行います。
	取得した経過時間とひとつ前のループ時に取得した時間の差分が
	1フレーム未満だった場合はSleep関数で処理を停止させます。