Win32API関数を使った、Windowsプログラムです。 最初は、メインウィンドウを作るプログラムです。
本などでよく見る Windowsプログラムです。 これは長いですが、ほとんど変わらないので、雛形として使えます。 ですから、これを、そのままコピペし、AppName にアプリケーション名を代入し、 後はコードを追加していくことになります。
/* よくあるウィンドウズプログラム */ #include <windows.h> #include <tchar.h> // グローバル変数 HINSTANCE hInst; // 現在のインターフェイス HWND hWnd; // ウィンドウハンドル // ウインドウプロシージャ宣言 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // アプリケーション開始位置 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int nCmdShow) { hInst = hInstance; // インスタンスハンドル保存 TCHAR AppName[] = L"win111"; // メインウィンドウのクラス名 TCHAR AppTitle[] = L"よくあるウィンドウズプログラム"; // タイトル MSG msg; // MSG構造体 WNDCLASSEX wc; // WNDCLASSEX構造体 // ウィンドウの定義 wc.cbSize = sizeof(WNDCLASSEX); // WNDCLASSEXのサイズ wc.style = CS_HREDRAW | CS_VREDRAW; // 拡張スタイル wc.lpfnWndProc = WndProc; // ウィンドウプロシージャ wc.cbClsExtra = 0; // 追加領域 wc.cbWndExtra = 0; // 追加領域 wc.hInstance = hInst; // インスタンス wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); // アイコン wc.hCursor = LoadCursor(NULL,IDC_ARROW); // カーソル wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); // 背景ブラシ wc.lpszMenuName = NULL; // メニュー wc.lpszClassName = AppName; // クラス名 wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // 小さなアイコン // ウインドウクラスの登録 if(RegisterClassEx(&wc) == 0) return 0; // ウインドウの作成 hWnd = CreateWindowEx( 0, // 拡張スタイル AppName, // クラス名 AppTitle, // キャプション WS_OVERLAPPEDWINDOW, // スタイル CW_USEDEFAULT, CW_USEDEFAULT, // 位置 CW_USEDEFAULT, CW_USEDEFAULT, // サイズ NULL, // 親ウィンドウのハンドル (HMENU)NULL, // メニュー hInst, // インスタンス NULL); // ウィンドウ作成データ if(hWnd == NULL) return 0; // ウィンドウの表示 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // メッセージループ while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ウインドウプロシージャ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { hWnd = hwnd; // ウィンドウハンドル保存 switch(msg){ case WM_DESTROY: PostQuitMessage(0); // メッセージループの終了 break; default: return DefWindowProc(hWnd, msg, wp, lp); // 既定のウィンドウプロシージャ } return 0; }
実行画面です。
このプログラムを書くと、上の図のようなメインウィンドウができます。 すると、OS から頻繁にメッセージがやって来ます。 ですから、そのメッセージに応えるようにプログラムを書くことになります。
深入りせず、さらっと見ていきます。
まず、WinMain関数です。 これは、Win32API 関数です。 アプリケーションはここからスタートします。 C/C++ の main 関数に相当します。 そして、{ } の中にプログラムを書いていきます。 WinMain関数を抜ければアプリケーションは終了します。
#include <windows.h> // グローバル変数 HINSTANCE hInst; // 現在のインターフェイス // アプリケーション開始位置 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { hInst = hInstance; // インスタンスハンドル保存 }
Windows のプログラムでは、HINSTANCE のような変な型が出てきます。 しかも沢山あります。 しかし、別名であって、元を辿れば int のような基本的な型になります。 ですから、コンパイルエラーになったとき、本来の型がわかります。
また、Unicode という文字コードがあります。 それに対応した書き方もあります。 これも別名になっているだけで、上と同じです。
#include <windows.h> #include <tchar.h> // グローバル変数 HINSTANCE hInst; // 現在のインターフェイス // アプリケーション開始位置 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int nCmdShow) { hInst = hInstance; // インスタンスハンドル保存 }
さて、どこかから、4つの変数が送られてきます。 2つは受け取ります。 hInstance は、このアプリケーションに割り当てられた値です。 Windows では、複数のアプリケーションが動いています。 それらを区別するためです。 これは、プログラムでも使うのでグローバル変数に保存しておきます。 次に、カッコの中を見ていきます。
まず、Windows に、メインウィンドウのクラスを登録します。 そのために、WNDCLASSEX構造体の変数を用意します。 この 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;
このメンバーに適当な値を代入します。 この値に従って、Windows はメインウィンドウを作ります。
// ウィンドウの定義 WNDCLASSEX wc; // WNDCLASSEX構造体 wc.cbSize = sizeof(WNDCLASSEX); // WNDCLASSEXのサイズ wc.style = CS_HREDRAW | CS_VREDRAW; // 拡張スタイル wc.lpfnWndProc = WndProc; // ウィンドウプロシージャ wc.cbClsExtra = 0; // 追加領域 wc.cbWndExtra = 0; // 追加領域 wc.hInstance = hInst; // インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // アイコン wc.hCursor = LoadCursor(NULL, IDC_ARROW); // カーソル wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); // 背景ブラシ wc.lpszMenuName = NULL; // メニュー wc.lpszClassName = AppName; // クラス名 wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // 小さなアイコン
ここでは、2つだけ説明します。
lpszClassName は、メインウィンドウの名前です。 ですから、何でもいいのですが、アプリケーション名でいいようです。
必要な値を代入したら、Windows に登録します。 (Windows に教えます) これは、RegisterClassEx関数を使います。 登録に失敗すると、0 が返ってきます。
// ウインドウクラスの登録 if(RegisterClassEx(&wc) == 0) return 0;
次は、登録したメインウィンドウを作ります。 このために、HWND型の変数を用意します。 これに、メインウィンドウのハンドルが入ります。 HWND型も見たこともない型ですが、何かの別名で、元を辿っていくと、基本的な型にたどり着くようです。 これもよく使うので、グローバル変数にしてあります。
HWND hWnd; // ウィンドウハンドル
メインウィンドウの作成は、CreateWindowEx関数を使います。 ここでも、引数に hInst や アプリケーション名を代入します。 返ってくる値は、メインウィンドウを指す値です。 この値をハンドルと言います。 この後、メインウィンドウを操作するとき、このハンドルを使います。 ですから、メインウィンドウ=hWndと覚えておきます。 作成できない場合は NULL が返ってきます。
// ウインドウ作成 hWnd = CreateWindowEx( 0, // 拡張スタイル AppName, // クラス名 AppTitle, // キャプション WS_OVERLAPPEDWINDOW, // スタイル CW_USEDEFAULT, CW_USEDEFAULT, // 位置 CW_USEDEFAULT, CW_USEDEFAULT, // サイズ NULL, // 親ウィンドウのハンドル (HMENU)NULL, // メニュー hInst , // インスタンス NULL); // ウィンドウ作成データ if(hWnd == NULL) return 0;
キャプションとは、ウィンドウの上に表示されるタイトル文字列です。 これは、AppTitle という変数にしました。
メインウィンドウを作っても、画面に表示されません。 表示させるには、ShowWindow関数と、UpdateWindow関数を使います。 さっそく、hWnd を使います。 また、WinMain関数で受け取った nCmdShow を引数にします。
// ウィンドウの表示
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
これで、画面にメインウィンドウが表示されます。
さて、メインウィンドウを開くと、OS からやたらとメッセージが送られてきます。 これを受け取るための変数を用意します。
MSG msg; // MSG構造体
このメッセージを受け取る部分がメッセージループです。 これも訳がわかりません。
// メッセージループ while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam;
ただ、メッセージを受け取ると、ウィンドウプロシージャを呼び出します。 このループを抜ければ、アプリケーションは終了します。
さて、メッセージを処理するウィンドウプロシージャは、WndProc です。 これは、WNDCLASSEX 構造体のメンバーに代入します。
wc.lpfnWndProc = WndProc; // ウィンドウプロシージャ
OS からメッセージが来るたびに、この WndProc が呼ばれることになります。 ですから、プログラムはここへ書くことになります。
// ウインドウプロシージャ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { hWnd = hwnd; switch(msg){ case WM_DESTROY: PostQuitMessage(0); // メッセージループを終了させる break; default: return DefWindowProc(hWnd, msg, wp, lp); // 既定のウィンドウプロシージャ } return 0; }
メッセージは、"WM_" で始まるマクロ定数を使います。 "winuser.h" の中に書いてあります。
ところで、大切なことがあります。 それは、アプリケーションを終わらせることです。
ウィンドウの "×" をクリックすると、WM_CLOSE メッセージが送られてきます。 すると、DefWindowProc関数はメインウィンドウを破棄します。 破棄されると、WM_DESTROY メッセージが送られてきます。 そこを捕まえて、PostQuitMessage(0) と書きます。 こうすると、メッセージループから抜け出して、きれいさっぱり終了します。
さて、DefWindowProc関数ですが、これ以外のメッセージも適当に処理してくれます。
これで解説は終わりです。 まあ、こんなもんだと思って下さい。 細かいことは、後回しにするのが賢明です。 大切なことは、アプリケーション名を書くことくらいです。
Windowsプログラムでは、不思議なデータ型が出てきます。 しかし、C/C++の型の別名です。 ただ、Win32 API 関数を使うときは不思議な型を使うべきでしょう。
C/C++ では、int は何ビットか固定されていません。 Win32API 関数の方は固定した作りになっています。 そのギャップを埋めるために工夫してあります。 表にすると次のようになります。
型 | 内容 |
---|---|
CHAR | char |
INT | signed int |
FLOAT | float |
VOID | void |
LPCHAR, PCHAR | char* |
これ以外にも沢山あります。 ただ、Win32API関数の定義に従うだけですから、覚える必要はないです。 また、"LPCHAR" のように、"LP" を付けるとポインタになります。 "P" を付けてもポインタになります。 ただ、どちらも同じ内容です。 これは他の場合もそうなっています。
文字列の方は複雑です。 文字コードの変更があったからです。 古い Windows95 では、マルチバイト文字、新しい Windows では、Unicode となりました。 そして、C/C++ では、コードを書く段階でどちらかにしなければならないのです。 それでは不便ということで、マクロが用意されています。 次の表は、UNICODEマクロが定義されている場合は、Unicode 対応となり、 そうでなければ、マルチバイト文字対応となります。
型 | Unicode | マルチバイト文字 |
---|---|---|
TCHAR | WCHAR | CHAR |
LPTSTR | LPWSTR | LPSTR |
LPCTSTR | LPCWSTR | LPCSTR |
また、"Hello" はマルチバイト文字、L"Hello" は Unicode です。 これは、TEXT マクロを使います。 TEXT("Hello") は、UNICODEマクロが定義されていれば、L"Hello" 、そうでなければ "Hello" と置き換えてくれるのです。
文字列を扱う関数も影響を受けます。 strcat はマルチバイト文字、lstrcat は Unicode です。 "l" が付くだけです。
ただ、"よくあるウィンドウズプログラム" では Unicode で書いています。