鳩でもわかるC#管理人はC#の存在とその素晴らしさに気づく前はC++を使っていました。そのときは猫でもわかるプログラミングというサイトをみて学習をしていました。WindowsアプリケーションをつくるためのWindows SDK編ではサンプルプログラムはC言語で書かれているのですが、WndProc関数関数がとにかく長い。短く書けないかと自分で別の書き方をしたりして試行錯誤を繰り返していたのですが、C#を使ったほうが速くアプリケーションが作れることに気づいてC++は完全にご無沙汰状態になっていました。

ところでなぜ「猫でもわかるプログラミング」なんてサイト名になっているのでしょうか?

それは同サイトのイントロダクションに書かれています。いわく

「猫の額」ほどの土地などと言います。猫の額は狭いのです。 額が狭いと言うことは、ゼントーヨー(前頭葉)が小さいと言うことです。 よーするに、賢くないと言うことです。
そんな「猫」でも書けるよう、難しい話は抜きの 実践中心のプログラミングの話です。

ということです。しかし今時の猫は前頭葉が小さいわりに賢いものです。人間がみてもなかなか理解できるものではありません。そこで猫よりも低い知能しか持たない鳩でもわかるように主にC#を扱った鳩でもわかるC#というサイトを立ち上げたのです。

新たな決意といきなりのハマりどころ

しかし、それでいいのか? やっぱり鳩のままで終わらずせめて猫並みの知能を手に入れるために久しぶりにC++に挑戦してみることにします。MFCなんて便利なものは使わず自分で一から書きます。

ところがブランクがあるからかいきなりハマり道へ。サンプルプログラムを入力してもエラーがでてコンパイルできないのです。

C++の空のプロジェクトを選べばよいと思ったのですが、これが間違い。正しくはWindowsデスクトップウィザードを選ばなければならなかったのです。

Windowsデスクトップウィザードを選択したあとプロジェクト名と場所を設定したあと「作成」ボタンをクリック。すると以下のようなダイアログが出てくるのでコンソールアプリケーションではなくデスクトップアプリケーションを選択して空のプロジェクトにチェックをいれて[OK]ボタンをクリックします。

そして追加メニューでC++ファイルを作成してイントロダクションにあるコードを丸パクリしてコンパイルしようとするとエラーになります(おいっ!)。

イントロダクションが書かれた当時(Mar/15/1997)はこれでも動いたのかもしれませんが、いまや20年以上の年月が流れています。当時のVisual C++と現在のVisual Studioでは仕様も違ってきています。

ここはこう書かなければならないのです。

クラスを使ってウィンドウを表示させる

ところでC言語とちがってC++ならクラスが使えます。クラスを使わないとグローバル変数ばっかりになって訳が分からなくなったり関数が乱立してわかりにくいコードになってしまいます。そこで以下のように書き直します。

MainWindowクラスを作成してWinMain関数は以下だけにします。

app.cpp

次にMainWindowクラスをどうするかですが、MainWindow.hというファイルをつくって以下のように書きます。

MainWindow.h

コンストラクタ

まずはコンストラクタですが、Createというウィンドウをつくるための関数を呼び出しているだけです。

MainWindow.cpp

ウィンドウを生成する

ではCreate関数はどうなっているのでしょうか?

まずWNDCLASSに必要なデータをセットしてRegisterClass関数で登録しています。myProg.lpfnWndProc = WndProcStaticとしてウィンドウプロシージャ関数のアドレスを指定しています。これを普通のメンバー関数にしたいのですが、その性格上できません。そこでWndProcStaticはスタティックなメンバ関数にしています。

そのあとWindowsAPI関数のCreateWindow関数を呼び出してウィンドウを生成します。ウィンドウは生成するだけでは表示されず表示するための関数を呼び出さないと表示されません。そこでShowWindow関数を呼び出して表示させています。

問題はCreateWindow関数の最後の引数にthisを渡している点です。これがちょっとしたポイントです。

MainWindow.cpp

ウィンドウプロシージャ関数

ウィンドウプロシージャ関数はメッセージを処理する関数です。WM_CREATEはウィンドウが生成されたときに送られてくるメッセージですが、ここでCreateWindow関数の最後の引数で渡したものが生きてきます。

LPCREATESTRUCT(lp)->lpCreateParamsとするとCreateWindow関数の最後の引数で渡したデータ(この場合はMainWindowのインスタンス)を取得できます。そしてこれをSetWindowLongPtr関数をつかってウィンドウ固有のユーザーデータをセットします。それ以降はウィンドウプロシージャ関数が呼び出されるたびにここからMainWindowのインスタンスのアドレスを取得できるようになります。またこのときにウィンドウハンドルをメンバ変数のm_hWndに格納します。

なにがしたいのかというとこれで普通のメンバー関数をウィンドウプロシージャ関数のように使えるようにするためです。最初の行でMainWindowへのポインタを取得できない場合はDefWindowProc関数を呼び出してデフォルトの処理をおこなわせます。ポインタを取得できたらメンバ関数であるWndProc関数を呼び出します。

MainWindow.cpp

WndProc関数が呼び出せるようになったらここで処理をおこないます。WM_CREATEメッセージが送られてきたらOnCreate関数を、WM_DESTROYメッセージが送られてきたらOnDestroy関数を呼び出します。

MainWindow.cpp

メッセージに対応してメンバー関数を呼び出す

ウィンドウが生成されたときの処理をOnCreate関数内に書けば動作させることができます。以下のコードはウィンドウ内にボタンを設置しています。

MainWindow.cpp

ボタンが押されるとWM_COMMANDメッセージが送られてきます。LOWORD(wp)でボタンのIDを取得できるのでOnCommand関数内で処理をします。ボタンが押されたら「ボタンが押されました」というメッセージボックスが表示されます。

ウィンドウの再描画がおこなわれるときにはWM_PAINTメッセージが送られてきます。OnPaint関数内になにかを書いていると描画の処理がおこなわれます。いまは特になにもしません。

ウィンドウが閉じられたらアプリケーションを終了させる

ウィンドウが閉じられたらウィンドウは消えますが、それだけではアプリケーションは終了しません。終了させるための処理をさせなければなりません。WM_DESTROYメッセージが送られてきたらPostQuitMessage関数を実行します。これでWinMain関数のなかのメッセージループから抜け出すことになるのでアプリケーションが終了します。

今回はOnPaint関数内になにも書きませんでしたが、次回はここに何かを書いて描画処理を行ないます。次回はC++でテトリスを作ってみようと考えています。