スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

C++とDirectX弄りの備忘録(1)

中途半端に暇だということが判明した。言い換えると、5日だと思ってた猶予は2週間ある。
そしてりテイクを繰り返した結果ソースコードが膨大になりすぎて管理ができない。
ので自分の脳内整理を含めて一つづつ情報を残しておくことにした。
書けば覚える、書かなきゃ覚えない。バグが出てもあきらめない。
効率がいい、これこっちの方がいいんじゃね、ってのがあったら教えてください。

1.名前空間
今回適当にやってみてわかったのは意外と名前衝突は起こる可能性が高いということ。
これを回避する方法、および同一目的の関数群やグローバル定数などをパッキングする目的として、
C++には名前空間が存在する。よってこれを自作のクラスおよび関数には設定することにした。
また、テストを目的とするなどでない限り、標準ライブラリの名前空間stdに関して
usingディレクテブ・宣言を使用してはならない。
ある程度のブロックが限定された中での使用は、場合によっては冗長になるためやむを得ないが、
グローバルエリアでのこれは、その後にインクルードされたすべてのヘッダおよびソースで
有効になってしまうという関係上絶対にやってはならない。


2.コンストラクタとデストラクタ
コンストラクタでは失敗を通知する方法として、戻り値が存在しない以上例外を利用するしかない。
もしくはis_succeedみたいなメソッドを設けて作成後に成否を見るというのもあるが。
どちらにせよコンストラクタには、例外の発生があるものとして使用を心掛ける。
しかしnewおよびmallocで確保されたメモリに関しては、これらをもれなく解放しなければならない。
基本的にWindowsの環境下であれば、プロセス終了とともに割り当てられたすべてのリソースは解放されるため、
終了時にはメモリリークが起こっていても問題はないが、定期的に生成されるオブジェクトにおいて、
これが発生することはリソースを食いつぶすことにつながりシステムの安定性を低下させる。
また、ゲーム機とかPICとかみたいに組み込み向けに作るならリークした時点でまずいと思う。

よってこれを回避するためにデストラクタを用いる。
デストラクタが呼ばれる条件は、オブジェクトがスコープから外れるときおよびdelete式であるが、
前者を利用してnewで確保したメモリのポインタに関しての管理を別オブジェクトに移譲する。
例えばstd::auto_ptrとか。C++にはJavaみたいに例外仕様にfinallyが存在しないため、
なるべくヒープから確保したメモリに関しては、そいつらのデストラクタと道連れに死んでもらう。
だからなるべくnewで確保した生ポインタを使用せず、scoped_aryだったりsmart_ptrを使う。
これによって、上位のクラスほどデストラクタでの解放処理を書く必要がなくなる。

2.ウィンドウクラス
とまあぐだぐだ心にとめておく方針を書いたところでまずはウィンドウから。
目標としては継承可能なプロシージャを作成すること。
Windowsでアプリケーションを作ろうとする以上最低限ウィンドウは必要。
コンソール上での動作でない限り、GUIを必要とするのならば間違いなく用意する必要がある。

まずウィンドウを作る際に最低限呼ばなければならないAPIは以下の2つ。

・RegisterClassEx() //「ウィンドウクラス」の登録 *C++のクラスじゃなく「ウィンドウ設定」な意味合い
・CreateWindow() //ウィンドウのインスタンスを作成

いろんなサイト見てると長いソースコードがあるが、呼ぶのは2つ。
こいつらの引数として渡す構造体のメンバが多量だったり、そもそも引数が多いのが原因。
ただ、多いからと言って複雑なわけではなくて、冷静になってみればごく簡単なことしか聞いていない。
前者はWNDCLASSEXという構造体にパラメータを入れて渡すだけだし、後者はパラメータが多いだけ。
しかも大体既定路線の設定がある。

そして作った後にはメッセージループを回さなければならない。
これはイベント駆動型であるため、何らかのイベントを捕捉する必要があるからだ。

よって、最低限何もしないウィンドウを作成し動作させるためには何が必要かというと、
ウィンドウクラス登録・インスタンス作成・メインループを回す、の3つがすべて。
ただ、メッセージループはウィンドウ自体の動作というよりはウィンドウを動かしている
というように見れるから、たぶんウィンドウクラスでやるべきなのはHWNDを得ること。
ウィンドウをクラスにまとめるにはたぶん「作る」ということさえ済ませればいいと思う。

ところで、作るのはいいとして、最も重要なのはウィンドウの動作。
それを制御する関数としてウィンドウプロシージャが必要となってくる。

LRESULT CALLBACK 任意の名前(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)

という形であればどんな関数でもいいが、グローバルな関数でなければならない。
つまりコールバックとなるためにアドレスが取れなければならない。
これがいろいろとめんどくさい問題をはらんでいる。

クラスにまとめる以上、そのウィンドウの動作であるプロシージャはメンバであるべきだし、
オーバーライドして変更できるべきである。
ただ、たとえば次のようにすると、この関数はプロシージャにできない。

これは、この関数がCWindowBaseのメンバであり、アドレスをとることができないから。
メンバ関数っていうのは、その内部でメンバ変数を変更する可能性がある以上、
実際に生成されたインスタンスとthisポインタで紐づけられているから、
静的な関数を要求するウィンドウプロシージャには指定できない。
ということで今度はこの関数を静的な関数にしてみる。

今度は静的にしたことにより、仮想関数化できない。
仮想関数にならないということは、継承時にその動作を変更することはできない。
この関数がこのウィンドウクラス以下のクラスのプロシージャに固定されてしまうことを意味する。
そして静的なメンバにはthisポインタは渡されない。
これは静的なメンバにはクラスのインスタンスがなくてもアクセスできるためで、
静的メンバの中で静的でないメンバ変数・関数にアクセスはできない。

通常のメンバはオーバーライドできるが、プロシージャにはならない。
静的なメンバはプロシージャになるが、オーバーライドはできない。

これらを解決するには、静的メンバが継承できるか、静的メンバが非静的メンバに
アクセスできる、すなわちthisポインタが渡されるかのどちらかが必要となる。
ここで、言語仕様的にも論理的にも前者は不可能だが、後者に関してはやれなくはない。
まずは次ようにメンバを設ける。

このようにした後、SetProp()/GetProp()/RemoveProp()の3つを利用してDummyProc()には、
関連付けられたウィンドウクラスのthisポインタを取得してもらう。
詳細はよくわからないが、ウィンドウはそれぞれ個別のプロパティリストなるものを保持しているらしく、
これらの関数を使うことによってそのリストへデータのハンドル(ポインタ)を登録・削除できる。
MSDNよりこの関数の引数に渡すデータを調べると次のようになっている(ほぼ丸コピペ。

HANDLE型ってのは、WinNT.hの中でtypedefされているので実質void*だから、
たぶん適当なポインタを入れるのは合法だと思う。lpStringには好きに入れればいい。
また、都合のいいことにプロシージャは関連付けられたウィンドウのハンドルを
第一引数として取得してくるので、hWndもプロシージャ単体で工面できる。
つまり、ウィンドウクラス登録後に

SetProp(hWnd, "THIS_PTR", static_cast(this));

でプロパティリストに自身のポインタを登録し、

CWindowBase* pWindow = static_cast(GetProp(hWnd, "THIS_PTR"));
pWindow->WndProc(hWnd, msg, wp, lp);

かなにかで、呼び出し元のウィンドウクラスのthisポインタを引き出して、
ローカルなプロシージャにリダイレクトしてやればいい。
あとは、こちらをみるとRemoveProp()しないとこれらは削除されないらしいので、折を見て消してやればいい。

以上をまとめると次のような形にするのが継承可能なプロシージャを持つ最低限の構成か。
まず宣言は次の通り。

実装は次のような感じ。

こんな感じで作ってやれば基本的な動作をするはず。今は最低限ということで、
あとは自分の大きさを取得させたり、変更させたり、タイトルキャプション設定したり…、
と適宜必要なメソッドを追加すればいいと思う。

さて、次回は入力か音声をまとめるか。



スポンサーサイト

Comment

No:222|nishi
テンプレートがカントリーチックになっとるw

コメントの投稿

Comment
管理者にだけ表示を許可する
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。