あまり、語ってばかりもいられないので、早速実習なのである。とにかく、Windowsのアプリを書いてみたい。BC++で、本当に大丈夫か…??などと心配している方は、ひとまず、これを試してみよう!
- Hello World!
最初は通例通り、Hello Worldにしておこう。
最初なので、リソース(メニューやアイコン)は無しとして、単にWindowフレームを1枚開いて、Hello World!と表示させるだけである。
ソースのダウンロード
↑試してみたい人は、ここからダウンロード
- コンパイルの方法及び実行
分割コンパイルでは無いので、MS-DOSプロンプトで、作業フォルダまで移動して、
BCC32 -W test001.cpp
を実行すれば良い。BC PADを使っている場合は、該当ファイルを読み込んで、[実行]→[コンパイル]を実行すれば良い。([実行]→[設定]→実行タグのコンパイル時パラメータに-Wが設定されている事!)
BC++が、問題無くインストールされていれば、test001.exeが生成される筈である。そのまま実行すれば、Windowが一枚開かれ、Hello World!と表示される。
- ソースの説明
ソースコードは大きく分けると2つの部分に分けることが出来る。WndProc()という関数と、WinMain()という関数である。この2つの関数は、今後、毎回必要になる基礎知識なので、若干長くなるが、それぞれの関数について細かく説明しておく。
- WinMain関数
実行順序から言うと、こちらが先に実行される事になる。(余談であるが、WndProcが先に書かれているのは、、WinMain関数にて、WndProc関数のアドレスを参照している部分が有るので、WinMainを先に書いてしまうと、WndProcをプロトタイプ宣言しなくてはならなくなり、単純に面倒だったという事である)さて、Windowsのアプリでは、最初にWinMain関数が呼ばれるので、この関数は必須となる。この関数の引数は、
int WINAPI WinMain(
HINSTANCE hInstance, … 現インスタンスのハンドル
HINSTANCE hPrevInstance, … 一つ前のインスタンスのハンドル
LPSTR lpCmdLine, … コマンドラインのストリングス
int nCmdShow … ウインドウの可視ステータス
);
となっている。
hInstanceは現インスタンスのハンドルとなるので、WNDCLASSの構造体にセットして、Windowsのシステムに登録しなければならない。
hPrevInstanceは、32ビット系の処理系では、常に0となるらしい。多分,16bit系からの移植を考えて残されていると思われるが、詳細については不明である。
lpCmdLineは、コマンドラインオプションを取得する為に使われると思うが、今回のHello Worldでは特別に使用しないので、記述しなかった。(使わないと、コンパイル時に警告となる為)
nCmdShowは、詳細不明だが、Windowを表示する場合にこの属性を使用する。
- まず、最初に現れる分岐[if(!hPrevInstance)]では、WinMainの第2引数が0の場合のみif文の中を実行するのであるが、今回は、32bitの処理系を考えているので、必ずtrueとなり、処理が実行される事になる。
これについて追記,
普通は、WNDCLASSに値をセットして、RegisterClassしなければ、Windowsのアプリとして動作しないのに、何故、この様に分岐させているのだろうか?と調べてみたら、このアプリを16ビット環境(Windows 3.1等)で動作させた場合,若しくはその環境でコンパイルした場合に、Windowが開けないので動作しないという造りにしている様である。即ち、これは32bitアプリとして造るのだよ!と暗に言っているのである。…自分は最初,この条件分岐は、hPrevInstanceがWinMainの中で使われていないので、ワーニングが出るのを防ぐ為と勘違いしていた。(2002.4.17追記)
|
WNDCLASSの構造体wcに対して、現在造ろうとしているWindowの概要(スタイル,アイコン,背景色,WndProcの所在等)を代入し、更に、現行のインスタンスのクラス名wc.lpszClassName(次に説明するCreateWindowの引数と同じであれば、名前は適当で良いと思われる)を付けて、RegisterClass(&wc) ;関数で、「こんなWindowアプリを実行します!」と宣言している。
- 次に、CreateWindow関数を呼び、開くWindowフレームの詳細を定義する訳だが、CreateWindowの引数は以下の通りである。
HWND CreateWindow(
LPCTSTR lpClassName, …クラス名,上記手順で定義した名前とする。
LPCTSTR lpWindowName, …タイトルバーに表示する文字列を定義
DWORD dwStyle, …ウインドウのスタイル
int x, …表示開始位置(フレームの左上x座標)
int y, …表示開始位置(フレームの左上y座標)
int nWidth, …Windowの表示幅(ドット数)
int nHeight, …Windowの表示高さ(ドット数)
HWND hWndParent, …親ウインドウへのハンドル
(無ければNULL)
HMENU hMenu, …子Window又は、メニューのハンドル
(無ければNULL)
HANDLE hInstance, …現インスタンスのハンドル
LPVOID lpParam …不明
);
サイズ云々については説明しなくても分かると思うが、ウインドウのスタイル等について、詳細が不明な項目が幾つか有ったので、サンプルのソースコードでは、他人の書いたサンプルをそのまま流用してみた。今後の学習で謎解きしようと思う。何はともあれ、この関数を実行する事によって、アプリは、Windowsの中に具体的な姿として確立されるので、この関数の戻り値として、現Windowフレームのハンドルを受け取る事が出来る訳だ。
- さて、具現化したWindowを表示しなければならない。次に行う,
ShowWindow(hwnd,nCmdShow) ;
UpdateWindow(hwnd) ;
の実行により、hwndでハンドルしているWindowフレームを表示させる事が出来る。なぜ、見せるというShowWindow()の後に、更新UpdateWindow()を行わなければならないのかは不明だが、ShowWindow()にて、属性を可視とし、UpdateWindow()にてWM_PAINTのイベントを発生させ、実際の描画を行っているのでは無いかと推測される。実際、WndProc内に、Windowフレームを描いて、"Hello World!"と表示させる部分が有るのだが、何もイベントが発生しない状態で、Hello World!の表示が行われるので、この推測は当たっていると思う。
- 次から始まるのが、いわゆるメッセージループである。
while(GetMessage(&msg,NULL,0,0)){
TranslateMessage(&msg) ;
DispatchMessage(&msg) ;
}
Windowフレームを表示した後、アプリケーションはユーザ操作若しくは何らかのイベント発生待ちの状態となるのである。何らかのイベントが発生するまで、GetMessage() ;関数の中で待ち、イベントが発生すると、GetMessage()が何らかの値を返すものと思われる。また、現インスタンスの終了(WM_DESTROY)が発生すると、その後のGetMessage()は0を返すので、このメッセージループを抜けて、アプリを終了するものとおもわれる。
ループ内で、TranslateMessage()が、GetMessage()にて取得したメッセージをWndProcにて使用出来る形に変換し、DispatchMessage() にて、WndProcへ送っていると思われる。
注)恐らく、WM_DESTROYのイベントが発生した場合でも、TranslateMessage(),DispatchMessage()の処理は行われ、次にGetMessage()が実行された時にループを抜けると考えるのが正しいと思われる。
- 最後にある,
return msg.wParam ;
にて、戻り値をWindowsのシステムに通知すると思われるが、詳細は不明。正常に終了されていれば、trueが返るのでは?と思われる。
- WndProc関数
WndProc()は、ウインドウ・プロシジャー(プロシージャ)と呼ばれ、Windowsアプリが動作中に発生したイベント・メッセージをフックして、実行を分岐させる関数であり、イベントによるプログラムのラウンチャ的存在と考えて良いだろう。
また、この関数名自体は、WinMainの最初に、WNDCLASSの構造体にセットされて、RegisterClass(&wc) ;にて登録されていなければならない。従って、サンプルではWndProc()としたが、名前に関しては、WindowProcでもWinProcでも良い。
この関数の引数については、基本的に下記のDefWindowProc関数と同じものとなる。
LRESULT DefWindowProc(
HWND hWnd, … ウインドウのハンドル
UINT Msg, … メッセージ種別(下記参照)
WPARAM wParam, … メッセージの第一パラメータ
LPARAM lParam … メッセージの第二パラメータ
) ;
プロシージャの造りは、基本的に、switch文にて、Msgを評価し、更に分岐する場合には、第一パラメータ,第二パラメータを評価する形を採れば良い。尚、WndProcがハンドル出来るイベントの何れの場合でも無かった場合(switch文でdefaultに当てはまる場合)には、DefWindowProc()の値を返す様に設計する。これをしないと、このアプリが全てのイベントをハンドルしてしまい、Windowsシステム自体に悪影響となりかねないので注意!
メッセージ種別
ここで使用したものと、良く使われるものを列挙する。
- WM_CREATE
アプリが具現化された場合に発生,(表示や、初期化する機能が無ければ、そのまま抜けて良い)
- WM_DESTROY
アプリが終了する場合に発生,終了処理を行って、PostQuitMessage(0) ;を実行する。
- WM_SIZE
Windowフレームがリサイズされた場合に発生,リサイズ自体の処理はWindowsのシステムが行うが、他にハンドルしているもの(リッチエディタ等)が有る場合には、そのリサイズを行う必要がある。
- WM_PAINT
フレームの描画を行う場合に発生,普通は、BeginPaint(hwnd,&ps) ;と、EndPaint(hwnd,&ps) ;の処理を行う。
- WM_COMMAND
メニュー等で[ファイル]→[開く]等を選択した場合に発生(リソースが必要)する。メニューにて選択された項目については、メッセージの第一パラメータ(wParam)を評価して、リソースに定義したIDにて分岐しなければならない。
- WM_TIMER
タイマのタイムアップの場合に発生する。
- TextOutについて
以上で、全体のプログラムの流れを記述したが、Windowフレームに、Hello World!を記述しているのは、WndProc内のTextOut()である。この関数は、計算結果の表示等に使用出来るので、他のイベントが発生した場合にも利用すれば、色々と使い道が有る。
サンプルのソースコード
|