「Win32/64アプリ開発 002 基礎の理解」の版間の差分
編集の要約なし |
(→概要) |
||
(同じ利用者による、間の15版が非表示) | |||
3行目: | 3行目: | ||
前の記事:[[Win32/64アプリ開発 001 基礎]] | 前の記事:[[Win32/64アプリ開発 001 基礎]] | ||
次の記事:[[Win32/64アプリ開発 003]] | 次の記事:[[Win32/64アプリ開発 003 基礎の理解続き]] | ||
== '''概要''' == | == '''概要''' == | ||
前回、示したコードを少しづつ理解してみましょう。全部を理解しようと思わない方がよいです。Win32/ | 前回、示したコードを少しづつ理解してみましょう。全部を理解しようと思わない方がよいです。Win32/64コード。未来永劫、少しでも多くを知ろうというスタンスで良いと思います。引き続き、C++(Cpp)を使います。 | ||
36行目: | 36行目: | ||
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); | LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);は関数のプロトタイプ宣言です。WinMainという関数がコンソールアプリでいうところのmain関数にあたりますが、WndProcのような関数はWinMain関数側で作成するWindowアプリが必ず定義する必要のあるウィンドウプロシージャーというメッセージ処理関数です。 | ||
45行目: | 45行目: | ||
wchar_t szClassName[] = L"SimpleEditWindow";ではウィンドウを作るときに名づけが必要になるので作ったワイド文字列です。適当な値でいいでしょう。L" " | wchar_t szClassName[] = L"SimpleEditWindow";ではウィンドウを作るときに名づけが必要になるので作ったワイド文字列です。適当な値でいいでしょう。L" "はwchar_t型のワイド文字列を設定するときに使う[[Cpp 文字列リテラル|リテラル]](変数をもたない文字列、変数を持たないとはいえ、リテラルの先頭アドレスを何らかの変数に代入して使います。)の定義方法です。ワイド文字列はマルチバイト文字列という方式に相対する文字列の方式です。ワイド文字は日本語のような文字コード体系の部分を保持するのに向いている文字列の形式で2バイトで1文字を表すようなUTF-16に似ているコード管理方式です。マルチバイト文字列でも日本語文字列を表現することは可能ですが、可変長の文字コード体系になります。Shift-JISのような方式です。 | ||
次に、HWND HWNDg_hEdit; HWND HWNDg_hWnd; | 次に、HWND HWNDg_hEdit; HWND HWNDg_hWnd;のように2つのHWND型グローバル変数を宣言しています。HWND型と聞くと、聞いたこともない新しい型のように思えますが、int型の変数を一つもつような構造体で、シンプルな新しい構造体変数の別名です。typedefキーワードで型名はいくらでも別名を持つことができます。新しい型名のほとんどは、C言語で習ったような変数で構成された型名が集まった構造体であるか、ただの別名に過ぎません。HWND型は主にWindowのハンドルと呼ばれるWindow番号を保持しているだけです。単なる数値です。考えすぎないことです。どんなに複雑なモノでも、0と1で全てが表現されるのがコンピュータです。謎めいたものなど存在しません。このウィンドウハンドルというものをグローバル変数で保持しておくと、いろいろなウィンドウプロシージャ関数をまたいで、同じ値のウィンドウハンドル番号をどこからでも参照できて便利なのでグローバル変数にしています。グローバル変数を使い始めると変数名の重複が起こってプログラム制作での障壁になりやすいと言われることがありますが、1000や2000のグローバル変数くらいは命名方法をきちんときめていれば、許容できるものです。この程度の数のグローバル変数を管理できないようであれば、変数を関数の中で管理してどこからでも呼び出せるややこしさには太刀打ちできません。現によくある、Webアプリの設定ファイルなんかにある、設定変数は1000や2000種類くらいあります。グローバル変数となんら変わりありません。 | ||
68行目: | 68行目: | ||
エントリポイントとなる関数がWinMain関数です。引数は4つですが、第2引数は16bitアプリの時代に同じアプリのメモリが共有されていたために、既に起動しているアプリの番号が入っていたようですが、もう使っていないので常にNULLだそうです。なので3つです。 | エントリポイントとなる関数がWinMain関数です。引数は4つですが、第2引数は16bitアプリの時代に同じアプリのメモリが共有されていたために、既に起動しているアプリの番号が入っていたようですが、もう使っていないので常にNULLだそうです。なので3つです。 | ||
=== WinMain === | |||
<span style="color:darkred; font-weight:bold;">■WinMain</span> | |||
'''第1引数:''' | '''第1引数:''' | ||
190行目: | 193行目: | ||
アイコンのイメージを読み込みます。準備しなくてもシステムで準備されている基本的な | アイコンのイメージを読み込みます。準備しなくてもシステムで準備されている基本的な | ||
モノを使うことも出来ます。LoadIcon(NULL, IDI_ICON1); | モノを使うことも出来ます。LoadIcon(NULL, IDI_ICON1);のようにすると、リソースエディタ標準で生成できるIDI_ICON1を指定できます。指定には、専用の関数が必要で難しいです。以下のようにLoadImage関数でも指定できます。格納される値自体はただのアイコン固有の数値です。その数値と関連づけられたビットマップイメージが内部で存在しているかという部分でただの数値とは異なる動作にはなります。適当な数値をいれちゃうと様々なチェックが走っているのでエラーが発生するもとになります。アイコンのように複雑な指定のものは大体こういった仕組みになっています。 | ||
272行目: | 275行目: | ||
具体的な色を指定するには | |||
(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 | WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH, NULL_BRUSH, HOLLOW_BRUSH | ||
でも、初期設定のこれだけでは、ウィンドウの全ての背景色描画がうまくいくわけではありません。色がチカチカしたり、他にもいろいろケアしないと駄目なことがあるということも覚えておくと良いでしょう。つまり、まだまだやることはある程度には心しておきましょう。 | |||
287行目: | 303行目: | ||
ウィンドウインスタンスの後に割り当てる余分なバイト数。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> | |||
CreateWindowEx関数は内部でCreateWindowExW関数かCreateWindowExA関数というものどちらかがマクロ定義で切り替わる仕組みで末尾がWのものは文字列引数がワイド文字列に末尾がAのものはマルチバイト文字列になるという違いがあります。末尾にExがついているものとつかないものもあります。引数の第1引数にDWORD型のdwExStyleがあるのがExのついている関数との違いです。それ以外の引数は同じです。今回のサンプルでは第1引数を0として、つかっていないのと同じなので、CreateWindow関数という名称の関数でも同じような動作になりますが、切り分ける必要もなく、ひとつ覚えればつぶしが効くという意味では、Windowの生成にはCreateWindowEx関数を使うのが主流だと思っていでよいでしょう。CreateWindowEx関数を使いながらもCreateWindow関数?そんなのあったな。と思えるのがツウなのかもしれません。 | |||
=== CreateWindowEx === | |||
<span style="color:darkred; font-weight:bold;">■CreateWindowEx</span> | |||
'''第1引数:''' DWORD dwExStyle | |||
拡張ウィンドウスタイル値を設定します。たくさんのフラグ定数がありますので、組み合わせて指定する感じになります。今回のサンプルでは0にしましたので、以下のフラグを設定したのと同じことになります。 | |||
WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR | |||
WS_EX_LEFT:ウィンドウには、一般的な左揃えプロパティがあります。 既定値です。 | |||
WS_EX_LTRREADING:ウィンドウ テキストは、左から右の読み取り順序プロパティを使用して表示されます。 既定値です。 | |||
WS_EX_RIGHTSCROLLBAR:垂直スクロールバー(存在する場合)は、クライアント領域の右側にあります。 既定値です。 | |||
その他の値は[https://learn.microsoft.com/ja-jp/windows/win32/winmsg/extended-window-styles https://learn.microsoft.com/ja-jp/windows/win32/winmsg/extended-window-styles]にありますが、どうせすぐにまたデッドリンクになります。Microsoftはコロコロ移動させるのが好きなのです。俺様は行きたいところにいく、どうしてもみたいなら、探せといっているのです。見たところで、引用したとおりのモノスゴクわかりにくい表現がならんでいるだけですけどね。Microsoftの全部のページのコピーに近いものを自分のサイトに置いた方が楽なのかもしれない。ここの管理人は誰も見もしないモノでも滅多にはコンテンツ移動はさせませんし。 | |||
'''第2引数:''' LPCWSTR lpClassName | |||
WNDCLASSのlpszClassNameメンバ変数に設定した値と同じものを設定する引数で、文字列型です。実は、ここで特定の予約された名前を使うとウィンドウが新たな機能を持ったものになります。でもメインウィンドウでは予約された名前を使わない方がいいと思います。メインウィンドウの中に特定の機能をもった領域を分割したりしながら組み込むのが良いと思います。どちらにせよ。各ウィンドウの配置やウィンドウサイズは計算がエグイほど大変です。複数のウィンドウの配置を管理する場合、配置の計算をするだけの関数はいると思いますね。予約された名前のことについてはMicrosoftのWebsiteの [https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-classes https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-classes] で説明が記載されています。システムの定義済みコントロールのクラス名と表現するのが正しいかもしれません。この表現で検索するとこのことについて知りたいと思った結果が返ってくるように思います。 | |||
'''第3引数:''' LPCWSTR lpWindowName | |||
ウィンドウタイトルになる文字列です。そういえば、VisualStudio2022にはタイトルバーがないですね。でも閉じるボタンとか最大化or元にサイズに戻す、最小化ボタンはメニューバーの右端にありますね。タイトルもlpClassNameで予約された文字を使うと、この値にも、その予約された文字に対して、どのような機能を提供するかを決定づける値にもなります。例えば、lpClassNameにAtlAxWin140を指定したときは、このウィンドウタイトル変数にShell.Explorer.2のような値を指定することでブラウザのような役割を持ったウィンドウが生成されます。ライブラリを使ったウィンドウ生成は制約が多いので自由にプログラムしたい場合はもっと基礎を知っていないと思うような機能は作れません。やりたいこととライブラリがバチッとはまっているなら便利ではあります。 | |||
'''第4引数:''' dwStyle | |||
基本となるウィンドウスタイルを設定します。第1引数の拡張ウィンドウスタイルよりも基礎的な構造について設定します。細かい個々の設定はマイクロソフトのWebに紹介されています。[https://learn.microsoft.com/ja-jp/windows/win32/winmsg/window-styles https://learn.microsoft.com/ja-jp/windows/win32/winmsg/window-styles] これもやはいところ、管理人のsiteでコピーしていきたいと思います。まるまるコピーするのはアレなので、すこし見やすくするくらいのことはしつつ移設しないとですね。サンプルではWS_OVERLAPPEDWINDOWという値を設定しました。これはWS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOXという値を設定したのと同じことを意味する値です。 | |||
'''第5引数:''' int X | |||
ウィンドウの左上隅の位置のX座標です。画面の左端が0で、右側ほど大きくなる値です。CW_USEDEFAULTという値を設定することもできます。 | |||
'''第6引数:''' int Y | |||
ウィンドウの左上隅の位置のY座標です。画面の上端が0で、下側ほど大きくなる値です。CW_USEDEFAULTという値を設定することもできます。 | |||
'''第7引数:''' int nWidth | |||
ウィンドウの幅です。CW_USEDEFAULTという値を設定することもできます。 | |||
'''第8引数:''' int nHeight | |||
ウィンドウの高さです。CW_USEDEFAULTという値を設定することもできます。 | |||
'''第9引数:''' HWND hWndParent | |||
親のウィンドウがあるときは設定しますが、最初に描くウィンドウでは親はないのでNULLです。子ウィンドウはこれ以降のウィンドウの生成で設定する必要がありますが、その場合はこのCreateWindowEx関数の戻り値の値が親であることを示すものになります。但し、小ウィンドウであってもポップアップ ウィンドウのような独立したウィンドウを生成することもできますので、必ずしも親の値が必要と言うわけでもありません。 | |||
'''第10引数:''' HMENU hMenu | |||
メニューへのハンドル。あるいはメニュー機能をもつような小ウィンドウなどを指定します。 | |||
'''第11引数:''' HINSTANCE hInstance | |||
アプリケーションの実体番号を設定します。メインウィンドウにはWinMain関数の第1引数で受け取った値を設定するのが普通です。全く関係ない値を設定するとアプリケーションを制御することはできません。 | |||
'''第12引数:''' LPVOID lpParam | |||
WM_CREATEというメッセージが送られた時にウィンドウプロシージャ関数が受け取るlpParam値と同じものです。ウィンドウプロシージャ関数の第4引数です。最初のウィンドウではWM_CREATEメッセージを受け取ってから生成するものではないので、NULLを設定します。次のWindow生成にlpParam値が必要なければNULLで良いです。 | |||
次に使う予定もないですが、メインウィンドウの生成で得たHWND型変数値とWinMain関数の第1引数であるHINSTANCE型変数値をグローバル変数に代入して保持しておきます。 | |||
<syntaxhighlight lang="cpp"> | |||
HWNDg_hWnd = HWNDhWnd; | |||
HINSTANCEg_hInst = hInstance; | |||
</syntaxhighlight> | |||
グローバル変数をファイル分割して扱う場合は、グローバル変数を参照するソースファイル全てが必ず読み込む*.hファイルを用意して、 | |||
<syntaxhighlight lang="cpp"> | |||
extern HWND HWNDg_hWnd; | |||
extern HINSTANCE HINSTANCEg_hInst; | |||
</syntaxhighlight> | |||
のように定義しておく必要があります。関数のプロトタイプ宣言に似たものを変数に対しても行う場合に必要になる手法です。グローバル変数の実体は各ソースファイルのグローバルな領域で定義すると良いです。「2か所での定義 + ヘッダファイルのインクルード」で管理しなければならないですね。 | |||
次に以下のような関数を定義して、ウィンドウを表示します。 | |||
<syntaxhighlight lang="cpp> | |||
ShowWindow(HWNDhWnd, nCmdShow); | |||
UpdateWindow(HWNDhWnd); | |||
</syntaxhighlight> | |||
ShowWindow関数で第1引数に指定したウィンドウの表示状態を第2引数で | |||
指定した状態に設定します。第2引数で指定できるキーワードも沢山ありまして、マイクロソフトのWebsite [https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-showwindow#parameters https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-showwindow#parameters] で紹介されています。 | |||
規定値ではWinMain関数から引数として 10 (SW_SHOWDEFAULT) をうけとります。 | |||
マイクロソフトのウェブサイトはバシッとデッドリンクになりやすいので早いうちに自分のところだけで説明が完結するように書き写さないといけないですね。頑張りますが、しばらくはマイクロソフトのページがつながることに望みを託します。 | |||
UpdateWindow関数では引数に指定したウィンドウが空でなければ、ウィンドウプロシージャ関数にWM_PAINTというメッセージを送信してくれます。 | |||
この後、Windowsはウィンドウプロシージャのメッセージを受け取るだけの無限ループに入ります。 | |||
<syntaxhighlight lang="cpp"> | |||
while (GetMessage(&MSGmsg, NULL, 0, 0)) { | |||
TranslateMessage(&MSGmsg); | |||
DispatchMessage(&MSGmsg); | |||
} | |||
</syntaxhighlight> | |||
負荷のかからない不思議な無限ループです。ウィンドウプロシージャ関数によるメッセージを受け取ってはプロシージャ関数の処理が開始されるという仕組みです。何故、負荷がかからないかというとメッセージを受け取るまでの間は一時停止(スリープ)をしている状態にあるからです。そして、ウィンドウ終了のメッセージを受け取るまで無限にループします。ループ内のプログラムコードは定石みたいなものです。間違えないようにしましょう。このコードがないのに実行してしまうと、負荷がかかりハングアップというか、なんというのか、コンピュータが暴走状態になり、保存していないものは、保存もできず、終わってしまうということになりかねません。 | |||
GetMessage(&MSGmsg, NULL, 0, 0)という関数について確認しておきます。 | |||
=== GetMessage === | |||
<span style="color:darkred; font-weight:bold;">■GetMessage</span> | |||
'''第1引数:''' MSG* = LPMSG lpMsg | |||
MSG型で既に実体がある状態の変数のアドレスを受け取ります。ここにOSから送られてくるメッセージが結び付けられて受け取ることができます。 | |||
'''第2引数:''' HWND hWnd | |||
メッセージを受け取る対象のHWND型の変数を受け取ります。NULLの場合はこのアプリケーションが含められるスレッドとウィンドウ全てのメッセージが受け取る対象になります。-1のときは、ウィンドウに対するモノは除外され、スレッドに対するメッセージだけになります。 | |||
'''第3引数:''' UINT wMsgFilterMin | |||
受け取るメッセージの番号の最小値 | |||
'''第4引数:''' UINT wMsgFilterMax | |||
受け取るメッセージの番号の最大値 | |||
GetMessage関数の戻り値がfalseになるような信号をうけとるとWindow処理の終了を意味し次のコードを実行して終了するという設定にしています。 | |||
<syntaxhighlight lang="cpp"> | |||
return (MSGmsg.wParam); | |||
</syntaxhighlight> | |||
最後に受け取るメッセージは WM_QUIT です。このwParam値を返すことになっています。マイクロソフトのWebsiteでもそう説明されています。[https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-winmain#return-value https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-winmain#return-value]。基本的には0値が返ることが正常終了だとみなされています。 | |||
ウィンドウプロシージャの部分の説明は次の記事で記述します。 | |||
292行目: | 511行目: | ||
前の記事:[[Win32/64アプリ開発 001 基礎]] | 前の記事:[[Win32/64アプリ開発 001 基礎]] | ||
次の記事:[[Win32/64アプリ開発 003]] | 次の記事:[[Win32/64アプリ開発 003 基礎の理解続き]] | ||
[[Win32/64_アプリケーション開発#記事|Win32/64_アプリケーション開発]]に戻る。 | [[Win32/64_アプリケーション開発#記事|Win32/64_アプリケーション開発]]に戻る。 |
2024年2月23日 (金) 03:20時点における最新版
前の記事:Win32/64アプリ開発 001 基礎
次の記事:Win32/64アプリ開発 003 基礎の理解続き
概要
前回、示したコードを少しづつ理解してみましょう。全部を理解しようと思わない方がよいです。Win32/64コード。未来永劫、少しでも多くを知ろうというスタンスで良いと思います。引き続き、C++(Cpp)を使います。
最初はインクルード文です。
#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のような関数はWinMain関数側で作成するWindowアプリが必ず定義する必要のあるウィンドウプロシージャーというメッセージ処理関数です。
Windowsアプリはウィンドウの構造をプログラムによって構築していく部分とOS側からのマウス操作やキー操作やウィンドウについている各種ボタンを操作したときに信号が送られるようになっています。これをウィンドウプロシージャーと呼ばれるメッセージ処理関数でどのような処理を行うかをコードで表現することで実現されます。ウィンドウを生成するときに、そのセットになるウィンドウプロシージャ関数名も設定するようになっています。
他にもウィンドウプロシージャ関数はダイアログボックスを作るときにも定義できるようになっていますので、ひとつのアプリケーションで複数のウィンドウプロシージャ関数によりメッセージ処理を行うことになります。
wchar_t szClassName[] = L"SimpleEditWindow";ではウィンドウを作るときに名づけが必要になるので作ったワイド文字列です。適当な値でいいでしょう。L" "はwchar_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つです。
WinMain
■WinMain
第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);
CreateWindowEx関数は内部でCreateWindowExW関数かCreateWindowExA関数というものどちらかがマクロ定義で切り替わる仕組みで末尾がWのものは文字列引数がワイド文字列に末尾がAのものはマルチバイト文字列になるという違いがあります。末尾にExがついているものとつかないものもあります。引数の第1引数にDWORD型のdwExStyleがあるのがExのついている関数との違いです。それ以外の引数は同じです。今回のサンプルでは第1引数を0として、つかっていないのと同じなので、CreateWindow関数という名称の関数でも同じような動作になりますが、切り分ける必要もなく、ひとつ覚えればつぶしが効くという意味では、Windowの生成にはCreateWindowEx関数を使うのが主流だと思っていでよいでしょう。CreateWindowEx関数を使いながらもCreateWindow関数?そんなのあったな。と思えるのがツウなのかもしれません。
CreateWindowEx
■CreateWindowEx
第1引数: DWORD dwExStyle
拡張ウィンドウスタイル値を設定します。たくさんのフラグ定数がありますので、組み合わせて指定する感じになります。今回のサンプルでは0にしましたので、以下のフラグを設定したのと同じことになります。
WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR
WS_EX_LEFT:ウィンドウには、一般的な左揃えプロパティがあります。 既定値です。
WS_EX_LTRREADING:ウィンドウ テキストは、左から右の読み取り順序プロパティを使用して表示されます。 既定値です。
WS_EX_RIGHTSCROLLBAR:垂直スクロールバー(存在する場合)は、クライアント領域の右側にあります。 既定値です。
その他の値はhttps://learn.microsoft.com/ja-jp/windows/win32/winmsg/extended-window-stylesにありますが、どうせすぐにまたデッドリンクになります。Microsoftはコロコロ移動させるのが好きなのです。俺様は行きたいところにいく、どうしてもみたいなら、探せといっているのです。見たところで、引用したとおりのモノスゴクわかりにくい表現がならんでいるだけですけどね。Microsoftの全部のページのコピーに近いものを自分のサイトに置いた方が楽なのかもしれない。ここの管理人は誰も見もしないモノでも滅多にはコンテンツ移動はさせませんし。
第2引数: LPCWSTR lpClassName
WNDCLASSのlpszClassNameメンバ変数に設定した値と同じものを設定する引数で、文字列型です。実は、ここで特定の予約された名前を使うとウィンドウが新たな機能を持ったものになります。でもメインウィンドウでは予約された名前を使わない方がいいと思います。メインウィンドウの中に特定の機能をもった領域を分割したりしながら組み込むのが良いと思います。どちらにせよ。各ウィンドウの配置やウィンドウサイズは計算がエグイほど大変です。複数のウィンドウの配置を管理する場合、配置の計算をするだけの関数はいると思いますね。予約された名前のことについてはMicrosoftのWebsiteの https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-classes で説明が記載されています。システムの定義済みコントロールのクラス名と表現するのが正しいかもしれません。この表現で検索するとこのことについて知りたいと思った結果が返ってくるように思います。
第3引数: LPCWSTR lpWindowName
ウィンドウタイトルになる文字列です。そういえば、VisualStudio2022にはタイトルバーがないですね。でも閉じるボタンとか最大化or元にサイズに戻す、最小化ボタンはメニューバーの右端にありますね。タイトルもlpClassNameで予約された文字を使うと、この値にも、その予約された文字に対して、どのような機能を提供するかを決定づける値にもなります。例えば、lpClassNameにAtlAxWin140を指定したときは、このウィンドウタイトル変数にShell.Explorer.2のような値を指定することでブラウザのような役割を持ったウィンドウが生成されます。ライブラリを使ったウィンドウ生成は制約が多いので自由にプログラムしたい場合はもっと基礎を知っていないと思うような機能は作れません。やりたいこととライブラリがバチッとはまっているなら便利ではあります。
第4引数: dwStyle
基本となるウィンドウスタイルを設定します。第1引数の拡張ウィンドウスタイルよりも基礎的な構造について設定します。細かい個々の設定はマイクロソフトのWebに紹介されています。https://learn.microsoft.com/ja-jp/windows/win32/winmsg/window-styles これもやはいところ、管理人のsiteでコピーしていきたいと思います。まるまるコピーするのはアレなので、すこし見やすくするくらいのことはしつつ移設しないとですね。サンプルではWS_OVERLAPPEDWINDOWという値を設定しました。これはWS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOXという値を設定したのと同じことを意味する値です。
第5引数: int X
ウィンドウの左上隅の位置のX座標です。画面の左端が0で、右側ほど大きくなる値です。CW_USEDEFAULTという値を設定することもできます。
第6引数: int Y
ウィンドウの左上隅の位置のY座標です。画面の上端が0で、下側ほど大きくなる値です。CW_USEDEFAULTという値を設定することもできます。
第7引数: int nWidth
ウィンドウの幅です。CW_USEDEFAULTという値を設定することもできます。
第8引数: int nHeight
ウィンドウの高さです。CW_USEDEFAULTという値を設定することもできます。
第9引数: HWND hWndParent
親のウィンドウがあるときは設定しますが、最初に描くウィンドウでは親はないのでNULLです。子ウィンドウはこれ以降のウィンドウの生成で設定する必要がありますが、その場合はこのCreateWindowEx関数の戻り値の値が親であることを示すものになります。但し、小ウィンドウであってもポップアップ ウィンドウのような独立したウィンドウを生成することもできますので、必ずしも親の値が必要と言うわけでもありません。
第10引数: HMENU hMenu
メニューへのハンドル。あるいはメニュー機能をもつような小ウィンドウなどを指定します。
第11引数: HINSTANCE hInstance
アプリケーションの実体番号を設定します。メインウィンドウにはWinMain関数の第1引数で受け取った値を設定するのが普通です。全く関係ない値を設定するとアプリケーションを制御することはできません。
第12引数: LPVOID lpParam
WM_CREATEというメッセージが送られた時にウィンドウプロシージャ関数が受け取るlpParam値と同じものです。ウィンドウプロシージャ関数の第4引数です。最初のウィンドウではWM_CREATEメッセージを受け取ってから生成するものではないので、NULLを設定します。次のWindow生成にlpParam値が必要なければNULLで良いです。
次に使う予定もないですが、メインウィンドウの生成で得たHWND型変数値とWinMain関数の第1引数であるHINSTANCE型変数値をグローバル変数に代入して保持しておきます。
HWNDg_hWnd = HWNDhWnd;
HINSTANCEg_hInst = hInstance;
グローバル変数をファイル分割して扱う場合は、グローバル変数を参照するソースファイル全てが必ず読み込む*.hファイルを用意して、
extern HWND HWNDg_hWnd;
extern HINSTANCE HINSTANCEg_hInst;
のように定義しておく必要があります。関数のプロトタイプ宣言に似たものを変数に対しても行う場合に必要になる手法です。グローバル変数の実体は各ソースファイルのグローバルな領域で定義すると良いです。「2か所での定義 + ヘッダファイルのインクルード」で管理しなければならないですね。
次に以下のような関数を定義して、ウィンドウを表示します。
ShowWindow(HWNDhWnd, nCmdShow);
UpdateWindow(HWNDhWnd);
ShowWindow関数で第1引数に指定したウィンドウの表示状態を第2引数で
指定した状態に設定します。第2引数で指定できるキーワードも沢山ありまして、マイクロソフトのWebsite https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-showwindow#parameters で紹介されています。
規定値ではWinMain関数から引数として 10 (SW_SHOWDEFAULT) をうけとります。
マイクロソフトのウェブサイトはバシッとデッドリンクになりやすいので早いうちに自分のところだけで説明が完結するように書き写さないといけないですね。頑張りますが、しばらくはマイクロソフトのページがつながることに望みを託します。
UpdateWindow関数では引数に指定したウィンドウが空でなければ、ウィンドウプロシージャ関数にWM_PAINTというメッセージを送信してくれます。
この後、Windowsはウィンドウプロシージャのメッセージを受け取るだけの無限ループに入ります。
while (GetMessage(&MSGmsg, NULL, 0, 0)) {
TranslateMessage(&MSGmsg);
DispatchMessage(&MSGmsg);
}
負荷のかからない不思議な無限ループです。ウィンドウプロシージャ関数によるメッセージを受け取ってはプロシージャ関数の処理が開始されるという仕組みです。何故、負荷がかからないかというとメッセージを受け取るまでの間は一時停止(スリープ)をしている状態にあるからです。そして、ウィンドウ終了のメッセージを受け取るまで無限にループします。ループ内のプログラムコードは定石みたいなものです。間違えないようにしましょう。このコードがないのに実行してしまうと、負荷がかかりハングアップというか、なんというのか、コンピュータが暴走状態になり、保存していないものは、保存もできず、終わってしまうということになりかねません。
GetMessage(&MSGmsg, NULL, 0, 0)という関数について確認しておきます。
GetMessage
■GetMessage
第1引数: MSG* = LPMSG lpMsg
MSG型で既に実体がある状態の変数のアドレスを受け取ります。ここにOSから送られてくるメッセージが結び付けられて受け取ることができます。
第2引数: HWND hWnd
メッセージを受け取る対象のHWND型の変数を受け取ります。NULLの場合はこのアプリケーションが含められるスレッドとウィンドウ全てのメッセージが受け取る対象になります。-1のときは、ウィンドウに対するモノは除外され、スレッドに対するメッセージだけになります。
第3引数: UINT wMsgFilterMin
受け取るメッセージの番号の最小値
第4引数: UINT wMsgFilterMax
受け取るメッセージの番号の最大値
GetMessage関数の戻り値がfalseになるような信号をうけとるとWindow処理の終了を意味し次のコードを実行して終了するという設定にしています。
return (MSGmsg.wParam);
最後に受け取るメッセージは WM_QUIT です。このwParam値を返すことになっています。マイクロソフトのWebsiteでもそう説明されています。https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-winmain#return-value。基本的には0値が返ることが正常終了だとみなされています。
ウィンドウプロシージャの部分の説明は次の記事で記述します。
前の記事:Win32/64アプリ開発 001 基礎