<戻る

ここまでできる超極短Windows基盤プログラム


 MS-DOSからWindowsにプログラムが移行した時、その余りの変わりように、当時ど素人であった私などは愕然としました。たかがウィンドウを表示するだけなのに、なんでこんな面倒なんだと思ったものです。でも実は、全然面倒ではなかったんです。ごちゃごちゃと色々書かれている余分な部分、またオブジェクト化によるプログラムの分断化で、その流れが見えていないだけでした。
 では、ウィンドウを表示させるだけの目的でプログラムを書くと、その面倒はどれほど短くなるのか?この章ではその限界を極めてみます。



@ ソース公開

 手っ取り早く出来上がりソースを見ていただきます。考え抜いた結果次のようになりました。尚、プラットフォームはVisual Studio 2005のウィンドウズアプリケーションです。

// 極短Windows基盤プログラム

#include <windows.h>
#include <tchar.h>

TCHAR gName[100] = _T("DirectX Ultra Short Basical Program");

LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam){
   
if(mes == WM_DESTROY) {PostQuitMessage(0); return 0;}
   return DefWindowProc(hWnd, mes, wParam, lParam);
}

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
   MSG msg; HWND hWnd;
   WNDCLASSEX wcex ={
sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance, NULL, NULL,
                                  (HBRUSH)(COLOR_WINDOW+1), NULL, (TCHAR*)gName, NULL};
   
if(!RegisterClassEx(&wcex)) return 0;

   
if(!(hWnd = CreateWindow(gName, gName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
         NULL, NULL, hInstance, NULL)))
     
return 0;

   ShowWindow(hWnd, nCmdShow);

   // メイン メッセージ ループ
   
while( GetMessage(&msg, NULL, 0, 0) ) DispatchMessage(&msg);

   
return 0;
}

 上のコメントと空白を含めた30行のプログラムは、空のプログラムに貼り付けると完全に実行できます(確認済みです)。実行すると通常のウィンドウが表示されますし終了も出来ます。WinProcプロシージャはなんとたった4行でOK。多分、Visual C++6やVisual Studio 2003などでも動作はすると思います。

 実質的にこれ以上縮める事は多分もうできません。どれか1つでも省略すると、プログラムは全く動きません。次の節で、プログラムの内容を簡単に解説します。まだWindowsプログラムに慣れていない方は流れが分かるかと思います。



A 解説

 まずはヘッダファイルです。

#include <windows.h>
#include <tchar.h>

 ウィンドウズプログラムを作るためにはwindows.hが必ず必要です。tchar.hには文字列を扱うマクロが色々と定義されています。これは、次の部分を認識させるのに必要です。

TCHAR gName[100] = _T("DirectX Ultra Short Basical Program");

 ここはアプリケーションの認識に必要な文字列をグローバル変数gNameに定義しています。これが無いとアプリケーションをOSに認識させることが出来ません。TCHARというのは2バイト文字列を表すwchar_t型のtypedefです。ポイントは「_T」というマーク。これはマクロでして、カッコ内に定義された文字列をwchar_t型として認識させてくれます。これが無いと、上のようにgNameに文字列を直接代入できません。

LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam){
   
if(mes == WM_DESTROY) {PostQuitMessage(0); return 0;}
   return DefWindowProc(hWnd, mes, wParam, lParam);
}

 これはウィンドウプロシージャ関数です。このグローバル関数が無いとWindowsプログラムは全く成立しません。この関数はOSの内部で自動的に呼ばれる「コールバック関数」であるため、戻り値や引数など書き方が厳密に決められています。msgには処理するべきメッセージが格納されてきます。ユーザはそのメッセージを判断して、しかるべき処理をすることになります。

 関数内部ではたった1つのメッセージだけを判断をしています。
 mesがWM_DESTROYの時は、PostQuitMessage関数を呼び出してアプリケーションを終了させます。この関数が呼ばれると、アプリケーションを完全に終了させるWM_QUITというメッセージが発行されます。WM_DESTORY自体はウィンドウを閉じたときに発行されるのですが、あくまでもウィンドウを消すだけなので、ここでWM_QUITを発行するようにしないとアプリケーションを永遠に終了させられません。裏で何か動いている不気味なアプリケーションになります(笑)

 それ以外のメッセージについてはすべてWindows APIであるDefWindowProc関数にその処理を一任しています。ウィンドウをただ表示させるためだけであれば、実はこれで十分なのです。再描画処理をするWM_PAINTも必要ありません。もちろん、拡張するときには必要ですよ(^-^;

  WNDCLASSEX wcex ={sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance, NULL, NULL,
                                  (HBRUSH)(COLOR_WINDOW+1), NULL, (TCHAR*)gName, NULL};
   
if(!RegisterClassEx(&wcex)) return 0;

 メイン関数に入ります。まず最初に行っているのは、自分のアプリケーションをOSに登録する部分です。そのためにはWNDCLASSEX構造体に正しい設定値を格納する必要があります。本来はwcex.lpszClassNameなどメンバ変数を指定して値を代入するべきなのですが、厳密に決められた構造体なので実は上のような初期化が可能です。極短プログラムにする工夫です。
 RegisterClassExに設定した構造体を渡すと登録完了です。不正があると全部はねられますので、判定文にすると安全です。

   if(!(hWnd = CreateWindow(gName, gName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
         NULL, NULL, hInstance, NULL)))
     
return 0;

   ShowWindow(hWnd, nCmdShow);

 ここはウィンドウハンドルを取得しています。ウィンドウハンドルが無いと、アプリケーションはウィンドウに対して全く何もできません。CreateWindow関数に渡している長い引数は、名前以外は全てデフォルトです。とりあえずウィンドウが欲しいという時には、こうするのが最短です。ここでも何か問題があるとhWndにNULLが入ってしまうので、判定文ではじくようにします。ShowWindowはウィンドウをを表示させるAPIです。

   // メイン メッセージ ループ
   
while( GetMessage(&msg, NULL, 0, 0) ) DispatchMessage(&msg);

 アプリケーションを定義して、ウィンドウハンドルを取得すると、あとはメッセージを処理するだけなのですが、そのメッセージ処理は1行で書けます!!while文の中で呼ばれるGetMessage関数は、蓄えられたメッセージを1つ取り出します。もしアプリケーションを終了させるWM_QUITだった場合、この関数は0を返します。それ以外だと0以外の整数が返ります。よって、終了させない限りループが実行され続けます。ループ内ではDispachMessage関数ただ1つが呼ばれています。この関数の役目はとても簡単でして、取得したメッセージをウィンドウプロシージャに送るだけです。逆に言えば、この関数が無いと、メッセージは一切処理されないので、アプリケーションはまるでハングアップしたかのような状態になってしまいます。
 
 通常メッセージループの中でTranslateMessage関数というのを呼び出します。これは、メッセージを固有の文字を表すメッセージに変換するのですが、画面を表示させるためだったり、キーボードからの文字を認識する必要が無いのであれば必要ありません。極短にはいらないのです。



 このプログラムは、実はDirectXのサンプルプログラムを掲載する基盤として作りました。実際にコピペで完全に動くプログラムというのは、ユーザにとって最良のチュートリアルになると思います。またDirectXのプログラムを手っ取り早く作りたい人にとっては、ある意味どうでも良いウィンドウ生成部分が短くできるというのは朗報かもしれません(^-^)



B デバッグできない場合

 空のプロジェクトに.cppファイルを生成し、ブレークポイントを設置してデバッグをした時、ブレークポイントで止まらない場合があります。これは、デバッグ情報が出力されていないためでして、プロジェクトのプロパティ内で以下の設定を変更するとたいがい直ります。


○ リンカのデバッグ
 デバッグ情報の生成を「はい(/DEBUG)」に変更


○ C/C++の全般
 デバッグ情報の形式を「エディットコンティニュ用プログラムデータベース(/ZI)」に変更

○ C/C++の最適化
 最適化を「無効(/Od)」に変更