今日はWin32APIについて勉強しました。
勉強のために「DirectX 12の魔導書」なる本を読もうと思ったが、これはWin32APIの知識がないと厳しそうだ!と判断し、Windowsプログラミングから始める決意をしたのである。。。
前編ではウィンドウの生成、後編では文字の描画とかについて書こうかと。(記事長すぎると読むとき嫌になるし)
当面の目標は、「文字の描画のみを使って自作のタイピング練習ゲームを作る」にします。(タイピングにおける最適化を訓練する専用のツールが欲しかった)
それができたらDirectX12の勉強に取り組み始めようかな~とか考えています。
Microsoftのリファレンスとか、必要に応じて偉大な先駆者の本とか記事とか読みながら進めていきます。
↓Microsoftのリファレンスへのリンク。これとにらめっこすることになりそう。
docs.microsoft.com
とりあえず今日勉強したこと。。。
アプリがどうやって動いているか、ウィンドウの生成方法、イベントの受け取り方、文字列の型について、文字の描画方法、とかとか
【OSによるアプリの管理】
どうやらOSからアプリに「メッセージ」として指令が発せられ、アプリの機能が動作するらしい(メッセージ駆動型)。
アプリ側は「メッセージループ」で常にメッセージの発生を監視していて、何か受信すると「ウィンドウプロシージャ」を実行する、という仕組みだそうな。
プロシージャってなんそれ!?と思ったので調べてみたら、「命令とか処理をまとめて外部から呼び出せる形にしたもの」、らしい。
【WinMain関数】
C言語プログラムとかC++プログラムではmainから始まってたが、Win32APIを使うとWinMainからプログラムが実行される。
docs.microsoft.com
という内容らしい。細かい内容はある程度勉強してから調べるつもり。
【ウィンドウの生成(WNDCLASSEX構造体)】
ウィンドウを生成するには、CreateWindow関数とShowWindow関数を使う。がその前に、ウィンドウの概要を設定するために「WNDCLASSEX」という構造体を宣言して値を設定する必要がある。
Microsoftのリファレンス見ると、WNDCLASSEX構造体の中身はこんな感じだって書いてあった。
typedef struct{
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
ウィンドウのスタイルにはいろいろあり、ここに使える定数が全部載ってた。
docs.microsoft.com
とりあえず今回は「CS_VREDRAW(0x0001)」と「CS_HREDRAW(0x0002)」の2つを使ってみる。
どちらもウィンドウの移動や、領域が変更されたときに再描画メッセージを送るためのもので、CS_VREDRAWが領域の幅が変更されたとき用、CS_HREDRAWが領域の高さが変更されたとき用らしい。
lpfnWndProcには、ウィンドウプロシージャ用に作った関数へのポインタを設定する。どうやらWin32APIポインタを意味する変数はlpから始まるっぽい。ウィンドウプロシージャ用の関数は後で作る(名前はWinProcにしとく)。
次の二つはよくわからん。追加のメモリ容量がなんとか、、、って書いてあったけど今のところどちらも0で良さそう。
HICONはLoadIcon(HINSTANCE hInstance, LPCSTR lpIconName)関数を使って設定できる。第一引き数にはNULL、第2引き数にはIDI_APPLICATIONを渡してみる。ここで使えるアイコンの定数もMicrosoftのリファレンスに載ってた。自前の画像とかも使えるのかな?今度調べてみよう。HCURSORもアイコンと似たような感じ。
今回は以下のように値を入れてみる。値を入れたらRegisterClassEx(PWNDCLASSEX)を呼んで設定すればよい。この関数はBOOL値を返してきて、設定に失敗したらfalseが返ってくるっぽい。
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_VREDRAW | CS_HREDRAW;
wcex.lpfnWndProc = WinProc;
wcex.cbClsExtra = wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPRICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = windowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPRICATION);
if(!RegisterClassEx(&wcex)){
return 1;
}
これができたら、CreateWindow関数を呼ぶ。
【ウィンドウの生成(CreateWindow関数)】
HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
引き数の内容はコメントの通り。。。
dwStyleはここから選ぶらしい。
docs.microsoft.com
とりあえず最小化、最大化、終了ボタンがついてるWS_OVERLAPPEDWINDOWにしてみよう。
hWndParentとhMenuは使わないのでNULL、hInstanceはWinMainの第一引き数で受け取ってた値を使う。
lpParamはよくわかんなかったけどNULLでおkっぽいのでNULLで。
HWND hWnd = CreateWindow(
windowClass, title,
WS_OVERLAPPEDWINDOW,
100, 100,
WIN_WIDTH, WIN_HEIGHT,
NULL, NULL, hInstance, NULL
);
うまくいかないとhWndにはNULLが入ってるらしい。
ここで返ってきたhWndと、WinMain関数の第四引き数をShowWindowに渡すとウィンドウが表示されるそうな。
【ウィンドウプロシージャの作成】
WNDCLASSEX構造体に設定するウィンドウプロシージャを作る。
ウィンドウプロシージャの作り方はここに書いてあった。
docs.microsoft.com
どうやら第二引き数にイベントの内容が入ってて、それに応じた処理を書けばよさそう。今回は終了イベントのみで十分なので、WM_DESTROYだけ見る。アプリを終了するときはPostQuitMessage関数を使ってOSに終了のメッセージを送ればよい。
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message){
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
};
【ウィンドウを実際に表示してみる】
必要なものは学習できたので、ウィンドウを表示するアプリを作ってみる。真っ白のウィンドウをただ表示するだけのシンプルなやつ。
ソースコードはこんな感じになった。
#include <windows.h>
#include <tchar.h>
#define WIN_WIDTH 400
#define WIN_HEIGHT 300
LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg){
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
}
return 0;
};
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nShowCmd)
{
TCHAR windowClass[] = _T("SampleApp");
TCHAR title[] = _T("さんぷる");
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_VREDRAW | CS_HREDRAW;
wcex.lpfnWndProc = WinProc;
wcex.cbClsExtra = wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = windowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if(!RegisterClassEx(&wcex)){
return 1;
}
HWND hWnd = CreateWindow(
windowClass, title,
WS_OVERLAPPEDWINDOW,
100, 100,
WIN_WIDTH, WIN_HEIGHT,
NULL, NULL, hInstance, NULL
);
if(!hWnd){
return 1;
}
ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
ウィンドウの生成はここまで。後編では文字列の描画をします。。。
【小言】
もっとごりごり進めたいなー。
DirectXの本読む前にGPUの勉強もしてみようかな。これとか良さそう。
グラフィック系でおすすめの本あったら教えてください。