「Win32/64アプリ開発 002 基礎の理解」の版間の差分
編集の要約なし |
編集の要約なし |
||
190行目: | 190行目: | ||
アイコンのイメージを読み込みます。準備しなくてもシステムで準備されている基本的な | アイコンのイメージを読み込みます。準備しなくてもシステムで準備されている基本的な | ||
モノを使うことも出来ます。LoadIcon(NULL, IDI_ICON1); | モノを使うことも出来ます。LoadIcon(NULL, IDI_ICON1);のようにすると、リソースエディタ標準で生成できるIDI_ICON1を指定できます。指定には、専用の関数が必要で難しいです。以下のようにLoadImage関数でも指定できます。格納される値自体はただのアイコン固有の数値です。その数値と関連づけられたビットマップイメージが内部で存在しているかという部分でただの数値とは異なる動作にはなります。適当な数値をいれちゃうと様々なチェックが走っているのでエラーが発生するもとになります。アイコンのように複雑な指定のものは大体こういった仕組みになっています。 | ||
300行目: | 300行目: | ||
ウィンドウインスタンスの後に割り当てる余分なバイト数。0値で埋まります。 | ウィンドウインスタンスの後に割り当てる余分なバイト数。0値で埋まります。 | ||
次に以下のようなRegisterClass関数を実行します。 | |||
<syntaxhighlight lang="cpp"> | |||
if(!RegisterClass(&WNDCLASSwc)) | |||
return FALSE; | |||
</syntaxhighlight> | |||
これで、CreateWindowEx関数を実行する準備が整ったことになります。次のような形式で実行され、それぞれの引数の意味を後述していきます。 | |||
<syntaxhighlight lang="cpp"> | |||
HWNDhWnd = CreateWindowEx( | |||
0, | |||
szClassName, | |||
L"Win32Applicationタイトル", | |||
WS_OVERLAPPEDWINDOW, | |||
CW_USEDEFAULT, | |||
CW_USEDEFAULT, | |||
CW_USEDEFAULT, | |||
CW_USEDEFAULT, | |||
NULL, | |||
NULL, | |||
hInstance, | |||
NULL); | |||
</syntaxhighlight> | |||
2023年10月18日 (水) 17:08時点における版
前の記事:Win32/64アプリ開発 001 基礎
次の記事:Win32/64アプリ開発 003
概要
前回、示したコードを少しづつ理解してみましょう。全部を理解しようと思わない方がよいです。Win32/64コード。未来永劫、少しでも多くを知ろうというスタンスで良いと思います。
最初はインクルード文です。
#include <windows.h>
#include "resource.h"
windows.hはwin32アプリ開発では、ほぼ必須となるような標準的なヘッダファイルです。コンソールアプリケーション開発で使ったcstdio.hやstdio.hのようなものと同等のモノです。
resource.hはリソースと呼ばれるアイコンやダイアログボックスのようなグラフィカルな要素を含む部品を利用するときに(プロジェクト名).rcでグラフィカルな要素をテキストベースで定義する作業をVisualStudioのような開発アプリでは、視覚的なUIのリソースエディタで操作することで、定義のコード自動生成するもので、resource.hにはリソース部品に使われるIDと呼ばれるテキストコードに対応する番号を管理するようなファイルで、リソースエディタで操作すると、自動で編集されるものです。最初のうちは、あまり理解できないでしょう。メニューバーやアイコンやダイアログボックスを表示させるようなアプリを作り始めたら、理解が深く進んでいくものです。
次に以下のような定義についてです。
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
wchar_t szClassName[] = L"SimpleEditWindow";
HWND HWNDg_hEdit;
HWND HWNDg_hWnd;
HINSTANCE HINSTANCEg_hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);は関数のプロトタイプ宣言です。WinMainという関数がコンソールアプリでいうところのmain関数にあたりますが、WndProcのような関数はWimMain関数側で作成するWindowアプリが必ず定義する必要のあるウィンドウプロシージャーというメッセージ処理関数です。
Windowsアプリはウィンドウの構造をプログラムによって構築していく部分とOS側からのマウス操作やキー操作やウィンドウについている各種ボタンを操作したときに信号が送られるようになっています。これをウィンドウプロシージャーと呼ばれるメッセージ処理関数でどのような処理を行うかをコードで表現することで実現されます。ウィンドウを生成するときに、そのセットになるウィンドウプロシージャ関数名も設定するようになっています。
他にもウィンドウプロシージャ関数はダイアログボックスを作るときにも定義できるようになっていますので、ひとつのアプリケーションで複数のウィンドウプロシージャ関数によりメッセージ処理を行うことになります。
wchar_t szClassName[] = L"SimpleEditWindow";ではウィンドウを作るときに名づけが必要になるので作ったワイド文字列です。適当な値でいいでしょう。L" "はwcha_t型のワイド文字列を設定するときに使うリテラル(変数をもたない文字列、変数を持たないとはいえ、リテラルの先頭アドレスを何らかの変数に代入して使います。)の定義方法です。ワイド文字列はマルチバイト文字列という方式に相対する文字列の方式です。ワイド文字は日本語のような文字コード体系の部分を保持するのに向いている文字列の形式で2バイトで1文字を表すようなUTF-16に似ているコード管理方式です。マルチバイト文字列でも日本語文字列を表現することは可能ですが、可変長の文字コード体系になります。Shift-JISのような方式です。
次に、HWND HWNDg_hEdit; HWND HWNDg_hWnd;のように2つのHWND型グローバル変数を宣言しています。HWND型と聞くと、聞いたこともない新しい型のように思えますが、int型の変数を一つもつような構造体で、シンプルな新しい構造体変数の別名です。typedefキーワードで型名はいくらでも別名を持つことができます。新しい型名のほとんどは、C言語で習ったような変数で構成された型名が集まった構造体であるか、ただの別名に過ぎません。HWND型は主にWindowのハンドルと呼ばれるWindow番号を保持しているだけです。単なる数値です。考えすぎないことです。どんんなに複雑なモノでも、0と1で全てが表現されるのがコンピュータです。謎めいたものなど存在しません。このウィンドウハンドルというものをグローバル変数で保持しておくと、いろいろなウィンドウプロシージャ関数をまたいで、同じ値のウィンドウハンドル番号をどこからでも参照できて便利なのでグローバル変数にしています。グローバル変数を使い始めると変数名の重複が起こってプログラム制作での障壁になりやすいと言われることがありますが、1000や2000のグローバル変数くらいは命名方法をきちんときめていれば、許容できるものです。この程度の数のグローバル変数を管理できないようであれば、変数を関数の中で管理してどこからでも呼び出せるややこしさには太刀打ちできません。現によくある、Webアプリの設定ファイルなんかにある、設定変数は1000や2000種類くらいあります。グローバル変数となんら変わりありません。
とはいえグローバル変数にしまくれと言っているわけではありません。その他の方法もありますので、バランスよく使っていきましょう。ここの管理人はグローバル変数を沢山使ってもいいと思っています。変に固執すると、グローバル変数で「すっ」とできることが、「ずっと」ややこしいことになります。ウィンドウズでは、アプリ構築の関数とメッセージ変数を扱うプロシージャ関数とに分かれています。構築側とプロシージャ側とで変数の受け渡しをするには、グローバル関数を使うのも楽な手段のひとつです。
HINSTANCE HINSTANCEg_hInst;も似ていますが、HWNDよりはひとつのアプリで扱う番号が少ないハンドルです。アプリの実体番号のようなものです。これもグローバル変数で管理されていたほうが楽なことが多い番号です。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInst,
LPSTR lpszCmdLine, int nCmdShow)
{
…省略…
}
エントリポイントとなる関数がWinMain関数です。引数は4つですが、第2引数は16bitアプリの時代に同じアプリのメモリが共有されていたために、既に起動しているアプリの番号が入っていたようですが、もう使っていないので常にNULLだそうです。なので3つです。
第1引数:
アプリ起動時に決められる他のアプリとは重複しない唯一の番号です。この番号を背負って、OS上を駆け巡るゼッケンのようなものです。
第2引数:
使われていません。
第3引数:
起動引数の文字列が格納されます。
第4引数:
起動時のウィンドウ表示状態を示す番号です。
どの引数も開発側としては、ありがたく受け取るのみです。
たいした説明もなく、次へ行きます。まずは、この部分を見てみましょう。
HWND HWNDhWnd;
MSG MSGmsg;
if (!hPreInst) {
WNDCLASS WNDCLASSwc = { 0 };
WNDCLASSwc.lpfnWndProc = WndProc;
WNDCLASSwc.hInstance = hInstance;
WNDCLASSwc.lpszClassName = szClassName;
WNDCLASSwc.lpszMenuName = NULL;
WNDCLASSwc.hCursor = LoadCursor(NULL, IDC_ARROW);
WNDCLASSwc.hIcon = (HICON)LoadImage(NULL,
MAKEINTRESOURCE(IDI_APPLICATION),
IDI_ICON1,
0,
0,
LR_DEFAULTSIZE | LR_SHARED);
if (!RegisterClass(&WNDCLASSwc))
return FALSE;
}
HWND HWNDhWnd; MSG MSGmsg; と二つの変数を定義しています。HWND型とMSG型です。MSG型は以下のような型の構造体です。
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
MSG型の変数は、後でWindowを待機させるループ処理の部分で使います。HWND型はウィンドウを生成するときの戻り値を受け取る部分で使います。肝心かなめのWindow生成関数と待機で必要となるので、必須といえるでしょう。
次に、if(!hpreInst)ならという条件式に移りますが、第二引数は未使用ですが、ほぼNULLで到達しますので、このIfの判定結果はほぼ、成立を意味するtrueになります。16bitのときだけ、メモリ共有のために必要なアプリケーションのインスタンス番号が格納されているのでしたね。このifの中で、ウィンドウ生成の前に必要なRegisterClass(HWNDCLASS&)という関数の実行が必要で、先にHWNDCLASS型変数WNDCLASSwcに必要な設定をしてから、RegisterClass関数を呼び出すことになります。WNDCLASSwcの構築がきちんとできていない場合は、Windowsアプリを起動するには不十分な状態なので、何も起動することなく、WinMain関数を終わらせる勢いです。
WNDCLASS WNDCLASSwc = { 0 };
WNDCLASSwc.lpfnWndProc = WndProc;
WNDCLASSwc.hInstance = hInstance;
WNDCLASSwc.lpszClassName = szClassName;
WNDCLASSwc.lpszMenuName = NULL;
WNDCLASSwc.hCursor = LoadCursor(NULL, IDC_ARROW);
WNDCLASSwc.hIcon = (HICON)LoadImage(NULL,
MAKEINTRESOURCE(IDI_APPLICATION),
IDI_ICON1,
0,
0,
LR_DEFAULTSIZE | LR_SHARED);
で、WNDCLASS WNDCLASSwc = { 0 }; として、WNDCLASSwcを宣言し、実体化します。次の行からはWNDCLASSのメンバ変数に関数ポインタや数値を代入する処理をして、必要最低限の設定をします。
lpfnWndProc:
ウィンドウプロシージャー関数の名前を設定します。あとで関数の実体をコーディングする必要もありますし、関数がまだ見つけれていない可能性もあるので、先にプロトタイプ宣言しておく必要もあります。プロトタイプ宣言は既にしてありましたね。プロトタイプ宣言がわからない人は、C言語の関数とプロトタイプ宣言について理解が必要です。
hInstance:
最初に生成するウィンドウに対してはWinMain関数の第一引数のアプリケーションハンドル値を設定します。ハンドルはただの番号でしたね。OSから割り当てられるアプリ起動番号です。
lpszClassName:
生成するウィンドウの名前みたいなものを定義します。タイトルバーに表示される文字列とは別のモノです。いずれお世話になる日もおとずれるかもしれませんが、今はめったにお目にかかれない裏側に隠された文字列と認識しておいてもよいでしょう。どれくらい役にたつものか知っている人もいるのかもしれません。
lpszMenuName:
メニューバーのリソースIDというものを設定します。ID文字列を設定するだけなので簡単です。
hCursor:
カーソルのイメージを読み込みます。準備しなくてもシステムで準備されている基本的なモノを使うことも出来ます。LoadCursor(NULL, IDC_ARROW);で、システム標準になります。指定には、専用の関数が必要で難しいです。以下のようにシステム標準の指定をLoadImage関数でも指定できます。
(HCURSOR)LoadImage(
NULL,
IDC_ARROW, IMAGE_CURSOR,
0, 0,
LR_DEFAULTSIZE | LR_SHARED);
hIcon:
アイコンのイメージを読み込みます。準備しなくてもシステムで準備されている基本的な モノを使うことも出来ます。LoadIcon(NULL, IDI_ICON1);のようにすると、リソースエディタ標準で生成できるIDI_ICON1を指定できます。指定には、専用の関数が必要で難しいです。以下のようにLoadImage関数でも指定できます。格納される値自体はただのアイコン固有の数値です。その数値と関連づけられたビットマップイメージが内部で存在しているかという部分でただの数値とは異なる動作にはなります。適当な数値をいれちゃうと様々なチェックが走っているのでエラーが発生するもとになります。アイコンのように複雑な指定のものは大体こういった仕組みになっています。
(HICON)LoadImage(NULL,
MAKEINTRESOURCE(IDI_APPLICATION),
IDI_ICON1,
0,
0,
LR_DEFAULTSIZE | LR_SHARED);
ここからは、今回のプログラムでは指定を省略した部分ですが、
hbrBackground:
ウィンドウの背景色を指定します。指定には、専用の関数が必要で難しいです。システムカラーを指定する場合は、(HBRUSH) (システムカラーキーワード + 1)のように指定します。システムカラーとはウィンドウズの現在の設定のどの部分という意味のキーワードです。使っている状態によって具体的な色は違います。(HBRUSH)(COLOR_BACKGROUND + 1)という感じで指定します。キーワードには以下のようなものがあります。
COLOR_SCROLLBAR | スクロールバーの軸 |
COLOR_BACKGROUND | デスクトップ |
COLOR_ACTIVECAPTION | アクティブウィンドウのタイトルバー |
COLOR_INACTIVECAPTION | 非アクティブウィンドウのタイトルバー |
COLOR_MENU | メニューの背景 |
COLOR_WINDOW | ウィンドウの背景 |
COLOR_WINDOWFRAME | ウィンドウの枠 |
COLOR_MENUTEXT | メニューのテキスト |
COLOR_WINDOWTEXT | ウィンドウのテキスト |
COLOR_CAPTIONTEXT | タイトルバーのテキスト |
COLOR_ACTIVEBORDER | アクティブウィンドウの境界 |
COLOR_INACTIVEBORDER | インアクティブウィンドウの境界 |
COLOR_APPWORKSPACE | MDI アプリケーションの背景 |
COLOR_HIGHLIGHT | 選択項目 |
COLOR_HIGHLIGHTTEXT | 選択項目のテキスト |
COLOR_BTNFACE | ボタンの表面 |
COLOR_BTNSHADOW | ボタンの影 |
COLOR_GRAYTEXT | 無効状態のテキスト |
COLOR_BTNTEXT | ボタンのテキスト |
具体的な色を指定するには
(HBRUSH)CreateSolidBrush(RGB(255, 0, 0));
のようにRGBで赤を意味する設定方法
(HBRUSH)GetStockObject(BLACK_BRUSH)
のような固定の色キーワードを使う方法があります。
以下のキーワードがあるようです。
WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH, NULL_BRUSH, HOLLOW_BRUSH
でも、初期設定のこれだけでは、ウィンドウの全ての背景色描画がうまくいくわけではありません。色がチカチカしたり、他にもいろいろケアしないと駄目なことがあるということも覚えておくと良いでしょう。つまり、まだまだやることはある程度には心しておきましょう。
cbClsExtra:
ウィンドウクラス構造体の後に割り当てる余分なバイト数。0値で埋まります。
cbWndExtra:
ウィンドウインスタンスの後に割り当てる余分なバイト数。0値で埋まります。
次に以下のようなRegisterClass関数を実行します。
if(!RegisterClass(&WNDCLASSwc))
return FALSE;
これで、CreateWindowEx関数を実行する準備が整ったことになります。次のような形式で実行され、それぞれの引数の意味を後述していきます。
HWNDhWnd = CreateWindowEx(
0,
szClassName,
L"Win32Applicationタイトル",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
前の記事:Win32/64アプリ開発 001 基礎
次の記事:Win32/64アプリ開発 003