※ 修正版をこちらのページにアップしています。将来的にはこのページは削除される可能性があります。
まずは、ウインドウを表示することから始めます。
実現しようとする機能をすべてコーディング(プログラミングすることです)してから、コンパイルしても問題はありませんが、どこかにバグが潜んでいた場合、特定が難しくなります。
このため、通常は、1番簡単なプログラムを作成して、徐々に機能を追加していきます。
つまり、「何もしない」ウインドウが現れるだけのプログラムを作り、そこにメニューやら、ツールバーやらがついて、さらにツールバーのボタンを押したときの動作がついてという感じで、一人前のプログラムにしていきます。
はじめの一歩である「何もしない」ウインドウが現れるだけのプログラムのソースを以下に記します。
そして入力後 「WinMain.cpp」 と名前を付けて保存してください。
/* ========================================================================== */ /* FILE WinMain.cpp */ /* CREATE 2006/10/01 */ /* MODIFY ----/--/-- */ /* REMARK */ /* COPYRIGHT 2000-2007, Latin. All Rights Reserved */ /* ========================================================================== */ #include <Windows.h> /* -------------------------------------------------------------------------- */ /* CALLBACK WndProc */ /* PARAM HWND hWnd : ウィンドウハンドル */ /* UINT uMsg : メッセージ */ /* WPARAM wParam : メッセージの追加情報 */ /* LPARAM lParam : メッセージの追加情報 */ /* RETURN LRESULT : */ /* メッセージ処理の結果 */ /* REMARK ウインドウプロシージャ */ /* -------------------------------------------------------------------------- */ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { /* 関数本体部 */ switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, uMsg, wParam, lParam)); } return 0L; } /* -------------------------------------------------------------------------- */ /* WINAPI WinMain */ /* PARAM HINSTANCE hInstance : 現在のインスタンス */ /* HINSTANCE hPrevInst : 直前のインスタンス */ /* LPSTR lpCmdLine : コマンドライン引数 */ /* int nCmdShow : ウインドウの表示形式 */ /* RETURN int : */ /* メッセージの wParam パラメータ */ /* REMARK Win32 アプリケーションの初期エントリポイント */ /* Windows システムが呼び出す。 */ /* -------------------------------------------------------------------------- */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { /* 変数宣言部 */ HWND hWnd; WNDCLASSEX WndClass; MSG Msg; /* 関数本体部 */ // ウインドウクラス作成 WndClass.cbSize = sizeof(WndClass); WndClass.style = CS_HREDRAW | CS_VREDRAW; WndClass.lpfnWndProc = WndProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = hInstance; WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); WndClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); WndClass.lpszMenuName = NULL; WndClass.lpszClassName = "FTPManager"; // ウィンドウクラス登録 if (! RegisterClassEx(&WndClass)) { return false; } // ウィンドウ作成 hWnd = CreateWindow(WndClass.lpszClassName, // 登録されているクラス名 "FTPManager", // ウィンドウ名 WS_OVERLAPPEDWINDOW, // ウィンドウスタイル CW_USEDEFAULT, // ウィンドウの横方向の位置 CW_USEDEFAULT, // ウィンドウの縦方向の位置 CW_USEDEFAULT, // ウィンドウの幅 CW_USEDEFAULT, // ウィンドウの高さ NULL, // 親ウィンドウまたはオーナーウィンドウのハンドル NULL, // メニューハンドルまたは子ウィンドウ ID hInstance, // アプリケーションインスタンスのハンドル NULL); // ウィンドウ作成データ if (! hWnd) { return false; } // ウィンドウの表示 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // メッセージループ while (GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return (int) Msg.wParam; }
ソースの入力が終わったら、実行してみてください。
次のような画面が表示されると思います。

「何もしない」ウインドウが表示されました。
「何もしない」ウインドウですが、ウィンドウとしての以下のような最低限の機能は備わっています。
コンソールアプリケーションでない限り、ウィンドウを表示させる必要があります。
このとき、上のソースは基本中の基本になりますのでそこで、このソースを雛形として保存しておくと、それを毎回使いまわすことができます。楽をできるところは、楽をしましょう。
また、実際に動いているソースですから、デバッグの手間が省けます。
Windows プログラミングにおいて、よく言われることですが、「全てを理解しようとしてもだめです。」
全てを理解しようとすることは、挫折への一番の近道なのです。
プログラマとは、「もやもや」を抱えたまま進んでいくしかないのです。常に「未知との遭遇」です。
かといって何も解説しないのもどうかと思いますので、ウィンドウを表示するための流れを簡単に説明すると、
の順になります。あれれ、WinMain() のコメントの部分ですね。
ウィンドウクラス とは何なのかというと、ウインドウの設計図です。
のような感じで、ウインドウの設計図を作成するのです。
設計図は、WNDCLASSEX構造体 を使って表しますから
このWNDCLASSEX構造体 の変数を作り、そのメンバ(.よりも右の部分です)に必要な情報を格納します。
次に、ウィンドウクラスの登録ですが、 RegisterClassEx() で行います。
ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx // クラスデータ );
WNDCLASSEX * つまり、WNDCLASSEX
のポインタを引数にする必要があるので、&(アドレス演算子)を使用して、WndClass を指すポインタを渡しています。
ウィンドウクラスの登録をしないと、そのウィンドウクラスの情報を元に、ウィンドウを作成することができません。
ところで、どこに登録するのでしょうか?それは、システム、この場合は、OS(この場合はWindows)に登録すると思ってください。
ウィンドウの作成は、 CreateWindow() で行います。
HWND CreateWindow( LPCTSTR lpClassName, // 登録されているクラス名 LPCTSTR lpWindowName, // ウィンドウ名 DWORD dwStyle, // ウィンドウスタイル int x, // ウィンドウの横方向の位置 int y, // ウィンドウの縦方向の位置 int nWidth, // ウィンドウの幅 int nHeight, // ウィンドウの高さ HWND hWndParent, // 親ウィンドウまたはオーナーウィンドウのハンドル HMENU hMenu, // メニューハンドルまたは子ウィンドウ ID HINSTANCE hInstance, // アプリケーションインスタンスのハンドル LPVOID lpParam // ウィンドウ作成データ );
第1引数に指定するウィンドウクラス名は、RegisterClassEx() で登録した ウィンドウクラス名です。
このウィンドウクラス名から、先に登録された設計図を OS(この場合はWindows)が呼び出してきてウィンドウが作成されるわけです。
CreateWindow() は、戻り値として作成したウィンドウの ウィンドウハンドル とを返します。
このウィンドウに対して何か処理をしようとするときは、するときはこのウィンドウハンドルが必要になります。
ウィンドウハンドルとは、何かしら仰々しい感じを与えますが、ただの数値(ポインタ)です。
ただの数値(ポインタ)なので表示させてみましょう。
![]()
今回のウインドウハンドルは、0x001505ec (1377772)を指しているようです。
失敗した場合には、戻り値で NULL を返します。
if (! hWnd) という書き方は、if (hWnd = 0) もしくは if (hWnd = NULL) と同じ意味です。
どうしてそうなるかというと、C言語では、次のような決まりがあります。
≠0 : 真
=0 : 偽
このため if (a == 0) を if (!a) と 記述できるのです。逆に、if (a != 0) は if (a) と記述できます。
この場合、 NULL は、0 なので if (! hWnd) は、if (hWnd == 0) ⇒ if (!hWnd) となるのです。
ウィンドウの表示には、 ShowWindow() を使います。
BOOL ShowWindow( HWND hWnd, // ウィンドウのハンドル int nCmdShow // 表示状態 );
第1引数に指定するウィンドウハンドルは、先ほど作成した、ウインドウハンドルです。
第2引数で指定する値は決まっています。 WinMain 関数が受け取った nCmdShowです。
なぜなら、ShowWindow()の解説の項に、「アプリケーションで初めて ShowWindow
関数を呼び出すときは、nCmdShow パラメータには、WinMain 関数が受け取った nCmdShow パラメータを指定してください。」
と書いてあるからです。
さらに、ウインドウの内容を再描画するため
UpdateWindow()
も呼び出しています。
さて、このソースの最後のほうにメッセージループなるコメントが書いてあるところがあります。
メッセージループって何でしょうか?
これは、ウィンドウズプログラムでは肝になるところです。
ウインドウに対して何か処理(ボタンを押す・マウスを動かす・文字を入力する…)を行うと、その情報は、一旦OS(この場合はWindows)が受け取ります。そして、システムからメッセージとして、各プログラムに送られ、
メッセージキュー と呼ばれるものに順番に蓄えられます。
そこで、 GetMessage() を使って、このメッセージキューから古いものから順番に取得、MSG構造体へと格納します。
BOOL GetMessage( LPMSG lpMsg, // メッセージ情報 HWND hWnd, // ウィンドウのハンドル UINT wMsgFilterMin, // 最初のメッセージ UINT wMsgFilterMax // 最後のメッセージ );
第2引数にウィンドウのハンドルを指定することで、そのウィンドウに関したメッセージのみ取得します。
そしてMSG構造体へと格納した後、キューからそのメッセージを削除します。
メッセージキューに何か溜まっている間取得し続けないといけないので、永久ループ処理で行います。
じゃー、いつ終わるの?と思われる方がいるでしょうが、GetMessage()の戻り値の項に、「WM_QUIT メッセージを取得した場合、0 が返ります。」とあります。
while (式) は、式が真 ⇒ 式 ≠ 0 の間有効になりますので、0 が帰ってきた時点で、ループ終了です。
なお、GetMessage()の警告の項に、「GetMessage 関数は、0 以外の値、0、-1 のいずれかを返します。
したがって、次のようなコードは避けてください。」として、先ほどのコードが書かれています。
が、-1 を返すのは現実的にほとんど起こりえないので、もっとも記述されている形式にしました。
気になる方は、メッセージループの部分を以下のように書き換えてみてください。
// メッセージループ while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { return bRet; } TranslateMessage(&Msg); DispatchMessage(&Msg); }
さて、受け取ったメッセージは、処理する必要があります。
受け取ったメッセージを処理するのが、
WndProc()
です。
LRESULT CALLBACK WindowProc( HWND hwnd, // ウィンドウのハンドル UINT uMsg, // メッセージの識別子 WPARAM wParam, // メッセージの最初のパラメータ LPARAM lParam // メッセージの 2 番目のパラメータ );
定義上は、WindowProc() なっていますが、「WindowProc
はアプリケーション定義のコールバック関数のプレースホルダであり、実際にこの関数名を使う必要はありません。」とありますので、関数の名前は何でもいいです。大事なのはこれが、システムから呼ばれるということです。
(このソース上、どこにも WndProc() を呼び出す記述がないことに注目してください。)
システムから呼ばれるということは、システムはこのプロシージャを、事前に把握しておかなければなりませんね。
そのため、設計図である WNDCLASSEX構造体 の lpfnWndProc で ウィンドウプロシージャ を定義しておきます。
じゃあ、どうやったらシステムから呼び出されるのでしょうか?
ウィンドウプロシージャへのメッセージの送出は、
DispatchMessage()
で行います。
送出するとは、システムがプロシージャをコールすることを意味します。
LRESULT DispatchMessage( CONST MSG *lpmsg // メッセージ情報 );
この DispatchMessage() でメッセージが送出されると、ウィンドウプロシージャの引数にそれぞれの値がセットされて呼び出されます。
さて、送られてきたメッセージを実際に処理するのは、WndProc() と書きましたが、
ここがウインドウズプログラムの中枢部で、ここの記述によってプログラムの挙動が決まります。
第1引数は、ウインドウハンドルで、作成したウインドウのハンドル(自分自身)です。
第2引数は、メッセージで「WM〜」という形ですが、これまた実際は単なる数値でしかありません。
そういうわけで、またまた表示させて見ましょう。
![]()
これは、WM_DESTROY のときの数値です。
ところで、ウインドウプロシージャには、必要・不必要を問わず、ウインドウ上に何か変化があるたびにメッセージが送られてきます。つまり、どのようなメッセージがウインドウプロシージャに送出されるかは未知数です。
そのすべてのパターンを網羅し、そのすべてにあった挙動を記述できるのでしょうか?
… 無理です。
そこで、用意されているのが、 DefWindowProc() です。
LRESULT DefWindowProc( HWND hWnd, // ウィンドウのハンドル UINT Msg, // メッセージの識別子 WPARAM wParam, // メッセージの最初のパラメータ LPARAM lParam // メッセージの 2 番目のパラメータ );
この関数に、ウインドウプロシージャが受け取ったデータを、そのまま渡してやります。
そうすると、後は万事取り計らってくれます。
これで、自分が処理したいメッセージにのみ専念できます。よかった、よかった。
最後に、メッセージループは、WM_QUIT メッセージを取得するまでと書きましたが、どうやったら、WM_QUIT メッセージが送られてくるんでしょうか?
残念ながら、どんなに待っていても送られて来ることはありません。そこで、送られるように仕向ける必要があります。
WM_QUIT のメッセージを発生させるには、
PostQuitMessage()
を使います。
VOID PostQuitMessage( int nExitCode // 終了コード );
PostQuitMessage() は、WM_DESTROY メッセージの処理中に記述します。
このため、ウインドウプロシージャには、自分が扱わないメッセージを処理する DefWindowProc() と終了時のメッセージ作成を処理するPostQuitMessage() は、最低限記述しておく必要があります。
本日のソース: FTPManager101(VC8).zip FTPManager101(BCC).zip
いやー、簡単に説明するつもりが、結構盛りだくさんになりました。
なんとなくしかわからなかった人、もしくは、まったくわからなかった人、気にしないでください。
ましてや、「自分は理解が悪いのであろうか、いやおつむが弱いに違いない。」なんて思ったりしないでください。
こういうものは、ある時フッと飲み込めるものです。ですが、なにもせずに飲み込めるものではありません。
このサイトでは、利便性を考えて、ソースを配布していますが、できれは、自分で入力してみてください。
コピー&ペーストもはじめのうちはしないほうがいいでしょう。
「習うより慣れろ」、何回もプログラムを書くこと以外に上達の近道はありません。