PeekMessageによる「メインループ」

Win32APIベースのVC++によるWindowsプログラミングでは、ウインドウを作成するとすぐに「メッセージループ」に入り、以降は「イベント」に応答する形で処理を進めます。しかし、このプログラミング方式はゲームなどプログラムの進行を自分で管理したい時には不便ですね。Windowsから送られてくるメッセージに応答するのではなく、常に独自の処理をしたいという事もあるでしょう。

そんな時は、プログラムの形をイベントに応答するのではなく、自分自身で駆動し続ける「メインループ」型に変えてしまいましょう。

メッセージループとメインループ

メッセージループとメインループは、イメージとしては以下のような感じです。メッセージループは、Windowシステムに適した「イベントドリブン」ベースのプログラミングなのですが、ウインドウシステムのメリットが少ないゲームなどでは、これが逆に制約にもなります。一方のメインループは、DOS時代のすべて自分で管理していたプログラミング手法に近いですね。

・メッセージループ

 ループ開始

   自分にメッセージが送られてくるまで待つ
   送られてきたメッセージをシステムに通知してメッセージ処理関数をコールバック
   もしプログラム終了のメッセージなら終了処理

 ループ終了


・メインループ

 ループ開始

  自分にメッセージが送られているか調べる
  送られていたらシステムに通知してメッセージ処理関数をコールバック
  もしプログラム終了のメッセージなら終了処理

  送られていなければ独自の処理を実行

 ループ終了

メッセージループでは、最初にメッセージが送られてくるまで「待つ」ため、メッセージが送られて来なければ何も出来ません。メッセージが送られて来て初めて、そのメッセージに応答する形で処理を実行出来るのです。一方、メインループではメッセージが送られてくるか「調べる」だけなので、常に自分で処理を行う事が可能です。

PeekMessage

プログラムにメッセージが送られているか調べるには、PeekMessageを使います。メッセージが送られていたら、後はメッセージループと同様にGetMessageでメッセージを取得(同時にメッセージキューからメッセージを削除)して、システムに渡しコールバック関数を実行させましょう。
以下のようにすると、呼び出したスレッドが持っている全てのウインドウを対象に、メッセージが送られているか調べる事が出来ます。

 PeekMessage (&msg,NULL,0,0,PM_NOREMOVE)

 ここでmsgは適当なMSG 構造体です。また、最後の引数をPM_REMOVEにするとメッセージがキューから削除されてしまう(つまりその後にGetMessage()を呼び出してもメッセージが取得できない)ので注意してください。PeekMessage はメッセージがあると0以外の値を返します。

メインループの例

  while (1) { /* メインループ */

      if (PeekMessage (&msg,NULL,0,0,PM_NOREMOVE)) {

          if (!GetMessage (&msg,NULL,0,0)) /* メッセージ処理 */
              return msg.wParam ;

          TranslateMessage(&msg);
          DispatchMessage(&msg);

      } else
          func(); /* 独自の処理 */

  }

プログラム

今回のプログラムはビットマップのスクロールです(スクロールについては、タイマーを使ったスクロール参照)。タイマーを使うと、精度の問題からやや荒くなってしまいますが、メインループから呼び出せば自由に速度を指定できます。スクロールの速度(処理の間隔)を指定するには、処理を行う関数の冒頭に


  if (GetTickCount()>tm+16)
      tm=GetTickCount();
  else /* 前回の処理から16ms 以上経ってなければ戻る */
      return;

 という処理を入れてやれば良いですね。GetTickCount()は起動してからの時間をミリ秒単位で返す関数なので、前回の処理から指定した時間(今回は16ms)が経たないうちは、新たな処理は行われません。ゲームなどでは、このように適当な「ウエイト」を入れて処理をする事になるでしょう。この値を変えるとスクロール速度も変わってくるので試してみてください。

プログラムソース表示