やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

描画Frameworkを考えてみる1

これまで描画Frameworkを作ってきた。 いろいろと実装パターンを考えていたが、多くなってきたので一度まとめてみる。 さいごに今後の方針をきめる。

描画Framework

ざっくり復習。

文字列や図形などを描画するためのAPIが存在する。 OSやそのバージョンなどによって様々な種類があり、それぞれ異なるインタフェースと機能をもつ。 現在はWindowsXPに絞ったAPIを対象としている。 GDI, GDI+, DirectX9, 3つのAPIがある。

APIには固有の初期処理などがある。 それをFrameworkによって自動化したい。 下記のインタフェースを継承し、引数オブジェクトを利用することで、すぐに描画できるようにしたい。

描画アーキテクチャ インタフェース
GDI GdiDrawer::Draw(HDC);(.h, .cpp)
GDI+ GdiPlusDrawer::Draw(Gdiplus::Graphics::Graphics*);(.h, .cpp)
DirectX9 DirectX9Drawer::Draw(LPDIRECT3DDEVICE9);(.h, .cpp)

また、新しい描画アーキテクチャも任意に追加できるようにしたい。 たとえばDirectX10やOpenGLなどを追加できるようにしたい。 既存のものは以下の通り。

描画アーキテクチャ インタフェース
GDI GdiWndProc::PartWndProc(...);(.h, .cpp)
GDI+ GdiPlusWndProc::PartWndProc(...);(.h, .cpp)
DirectX9 DirectX9WndProc::PartWndProc(...);(.h, .cpp)

現状のコード

現状のソースコード。 以下で列挙した実装パターンでいうところのC,H。

問題点

問題点は、描画アーキテクチャや描画内容のクラスを追加するにはGraphicsSelectorクラス(.h, .cpp)を書き換えねばならないこと。 詳しくは前回を参照。

実装パターン

これまで考えてきたパターン

(A). IDrawWndProc実装クラス内でSelectする(GdiWndProc::SelectDrawer(string key)

(B). DrawWndProcクラス内でSelectする(DrawWndProc::Select(string drawWndProcKey, string drawerKey);

(C). 描画アーキテクチャごとにDrawerインタフェースを用意する(IGdiDrawer::Draw(HDC hdc);

(D). Selectorで描画デバイスごとに分ける(Selector<HDC>

(E). C++のRIITで分岐する(typeid(HDC)

(F). staticなメンバ変数にしてしまう(DirectX9::m_LPDIRECT3DDEVICE9

(G). Draw関数の引数をクラスにする(IDrawer::Draw(DrawDevice device)

(H). SelectDrawerを関数ポインタで共通化する(GraphicsSelectorクラス)

未知のパターン

(I). 動的コンパイルでGraphicsSelectorクラスを動的に生成する

(J). #defineでソースコードのテンプレートを作成し、量産する

  • 関数を生成したりはできない?
  • コンパイル時点で固定する。文字リテラルで書かねばならず動的テキストから生成できない

(K). リフレクションライブラリ。未確認。

(L). DynamicPatcher。未確認。

(M). 既存ライブラリはないのか?未確認。

(A),(B)

(A). IDrawWndProc実装クラス内でSelectする(GdiWndProc::SelectDrawer(string key)

(B). DrawWndProcクラス内でSelectする(DrawWndProc::Select(string drawWndProcKey, string drawerKey);

(A),(B)は依存関係の点からいって自然。

GdiDrawWndProcはGdiDrawerを所持する。 他の描画アーキテクチャでも同じ関係になる。

~IDrawWndProc ~Drawer
GdiWndProc GdiDrawer
GdiPlusWndProc GdiPlusDrawer
DirectX9WndProc DirectX9Drawer

ただ、IDrawWndProcは描画こそが本来の機能であるにもかかわらず、Selectという選択の機能をもたせるのがいただけない。 描画の準備と、描画の選択は別にしたい。

たとえば、今描画の選択はGraphicsSelector(.h,.cpp)でやっている。 KeyboardWndProc(.h,.cpp)にGraphicsSelectorクラスのポインタを渡しているから、キーボードで描画の選択ができる。

もし選択の機能をDrawWndProcにしてしまったら、DrawWndProcをKeyboardWndProcに渡すことになってしまう。 すべてのPartWndProcはあくまでWndProcを分割したものである。PartWndProc同士で直接の依存関係を持たせたくない。

したがって、(A),(B)は不採用。

(C),(D),(E)

(C). 描画アーキテクチャごとにDrawerインタフェースを用意する(IGdiDrawer::Draw(HDC hdc);

(D). Selectorで描画デバイスごとに分ける(Selector<HDC>

(E). C++のRIITで分岐する(typeid(HDC)

(C),(D),(E)は結局if文で型ごとに分岐せねばならない。 現状ではGraphicsSelector(.h,.cpp)がその部分を担っている。

IDrawWndProc継承クラスやDrawerクラスが増えたら、分岐などの処理を増やさねばならない。

今は(C)と(H)を採用している。 したがって、今まさに問題になっていることである。 これを改善したいのだが、どうしたものか。

(F),(G)

(F). staticなメンバ変数にしてしまう(DirectX9::m_LPDIRECT3DDEVICE9

(G). Draw関数の引数をクラスにする(IDrawer::Draw(DrawDevice device)

(F),(G)はインタフェースの意味が半減する。 (F)はどこからでも参照できるようになってしまうため、もはやDraw()の引数を差別化する必要もない。 Drawerのインタフェースを共通化できる。 めでたくGraphicsSelector(.h,.cpp)で同じようなコードの追加を不要にできると思う。

しかし、実装側でどのデバイスをどうやって参照するか知らないと書けない。

(G)も(F)と同様。単にstaticメンバ変数から引数になっただけ。 Drawer実装クラスでdevice.m_hdc, device.m_graphics, device.m_lpDirect3dDevice9のように参照せねばならない。 描画アーキテクチャを選択できているならデバイスはひとつに特定できるはずだし、特定すべき。 特定できないのならFrameworkとはいいがたい中途半端なものになってしまうだろう。

したがって、(F),(G)は不採用。

(H)

(H). SelectDrawerを関数ポインタで共通化する(GraphicsSelectorクラス)

(H)は(C)の改善版。でも結局GraphicsSelector(.h,.cpp)内で似たようなSelect~Drawer(string drawerKey)実装をせねばならないから根本解決にはならない。

描画アーキテクチャごとにDrawerのInitialize, Select, Finalize, を実装する関数をつくらねばならない。 たとえばGDI, GDI+, DirectX9, のほかにOpenGL描画アーキテクチャが実装されたら、それ用のSelectDrawer関数をつくらねばならない。それはGDIなど他の描画アーキテクチャのSelectDrawerと同じような内容である。

今は(C)と(H)を採用している。 したがって、今まさに問題になっていることである。 これを改善したいのだが、どうしたものか。

(I)

(I). 動的コンパイルでGraphicsSelectorクラスを動的に生成する

C#ならリフレクションや動的コンパイルなどを使って解決できると思った。 最も理想に近いものををかなえる方法かもしれない。 でも、C++でできるかどうかもわからない。 すこし妄想してみる。

できればGraphicsSelector.AppendDrawArchitecture("OpenGL", new OpenGLWndProc(), new Selector<OpenGLDrawer*>);のようにするだけで利用できるようにしたい。

もっといえば、一行も追加したくない。 所定のフォルダ配下に所定のインタフェースクラスを継承しているものがあったら自動で登録して使えるようにしたい。 つまり動的コンパイル&実行。こんなことがC++でできるのだろうか。

http://0xcc.net/blog/archives/000068.html

動的ロードによってプログラムが自分自身を拡張できる。 プログラム自体が C や C++ でルーチンを書き、コンパイラとリンカを実行して共有ライブラリを作成して、新しいコードを動的にロードして実行することも考えられる。

これはLinuxでの話。Windowsで同じようにできるかどうかは知らない。 そして、あきらかに今の自分の身の丈をはるかに超えた規模と難度になることは間違いない。

したがって、すぐ作業にとりかかることはできない。 でも、一番やってみたい有力な候補

(J)

(J). #defineでソースコードのテンプレートを作成し、量産する

  • 関数を生成したりはできない?
  • コンパイル時点で固定する。文字リテラルで書かねばならず動的テキストから生成できない

これは思いつかなかった。簡単にできそう。 でも、文字列の置き換えしかできない。 動的に生成した文字列から生成できない。

文字列なので、コンパイルしたらどうなるのかよくわからない。 あまり長々と複雑なコードは書けない。書きたくない。

方法(C)や(D)と組み合わせることを考えたが、今の実装ではむずかしそう。 classのメンバ変数やメンバ関数を追加する必要がある。 この方法(J)では置き換えはできても追記はできない。 もし、javascriptのeval関数のように動的な文字列を実行できるならば可能だったかもしれないが。

したがって(J)は不採用。

(K)

(K). リフレクションライブラリ。未確認。

今回のやりたいこととは違うか。 コードを見てないからよくわからないが、一旦パス。

(L)

(L). DynamicPatcher。未確認。

.h, .cpp ファイルからコンパイルし、.pdb, .objファイルを生成する。 これを用いてリンクするらしい。 .objファイルの仕様は公開されているらしい。それを元にリンクするのだとか。

C++/Win32でも使える?C++/CLIでしか使えない?

それに、DynamicPatcherは、

既存のほぼ全ての関数を差し替えられる

もの。

今回考えていた、新しいclassを文字列から生成したりするようなものではないっぽい。 というか、そんなことできるのかどうかもわからない。 第一、そんなことしたら既存コード側が動的生成されたコードをどうやって参照するのか困りそう。

今の設計では、Select(string, string);の文字列だけでアクセスできるから、動的生成されたclassの型とか.hとかは必要ないはず。でも、GraphicSelector.hだけは必要か。

どうも知識がなさすぎて具体性に欠ける。これ以上はやってみるしかない。 試しにやってみたい候補

(M)

(M). 既存ライブラリはないのか?未確認。

これを確認するのは大変そう。 今回の用途に使えるのかどうか確認するだけでも面倒そうだ。 規模も大きそう。 概要の把握すら今の自分の技術力ではむずかしい予感。 とりあえずパス。

方針

今後の方針は以下のどれかになりそう。

(a). Frameworkは一旦終わり。ここらで妥協する

(b). DynamicPatcherが動くか確認してみる

(c). 動的コンパイルの自前実装について妄想する

(d). コンパイラについて情報収集する

(e). C++イディオムについて情報収集する

まず、(a)はくやしいからヤダ。

(b)は余裕があればやってみたい。 実際に動くものがなければ、できるかどうかすらわからない。動くものを作って実現できることを確認したい。

(b)で動いたら、これを基にして動的コンパイルによるFrameworkを実装してみてもいいかもしれない。 やりたいことはこれだけではなさそうだから、何が必要なのかが見えてきそう。

でも、やっぱり自分でつくってみたい。 (b)をやろうがやるまいが、最終的には自分で作りたい(c)。 ただ、こんなディープな内容に首を突っ込むほどの実力はまだない。

自作するより前に、コンパイラについて知っておきたい(d)。 clangやgccなどのほかのコンパイラや、VC++コンパイルコマンドcl.exeの使い方。makeファイル。DLLの作成と明示的リンク。さすがにobjファイルの仕様までは把握しなくていい。キリがない。

また、それよりも前に、C++イディオムについて学んでおきたい(e)。 Frameworkのような再利用性の高いコードを書くなら、もう少し学習が必要な気がしている。 スマートポインタなど、気になるものがいくつかある。いろいろ学んでおきたい。 既存のコード改善にもなるかもしれない。 今は(e)が現実的か。