やはりくそい

とりあえずVCが2008→2010になったので今までのdx9libを動くようにする。さすがにogg/vorbisの安定リリースにVS2010のプロジェクトが入っていた。というわけで再ビルド安定。なんかDirectXが勝手にインクルード・ライブラリパスを通してくれなくなった(というかVS本体へのパス設定が撤廃されて全部プロジェクトに保存することになった?)のでインクルードできないだのlibが見つからなくてリンクできないだのにひたすらパスを通す作業。しばらく(かなり)時間が経ってから見たので気になったところをちょいと直すが無駄に長くて一番上が分かりにくいエラーとなかなか終わらないビルド(エラーを吐き続けてるだけ)のせいで世紀末に。ビルド中に上書き保存しようとするとなんか言われるし…。
まあ、スタティックライブラリのビルドまではできた、が、それを使ったexeの生成時に標準ライブラリの定義重複リンカエラー多発。oggの方のプロジェクトオプションもゴタゴタやるが無理ゲー。最終的にはなんかこれogg/ogg.hが見つからないってエラー出てね?→パス通してビルド成功確認したらexe(dx9libtest)のエラーがなくなった。なにこれ。ビルド失敗したのに変な.libが出力されててそれとリンクしてた?昔の.lib参照してた?まったく分からない…。


ふーん、DX9LibErrorがあって、そこからDXError(DirectXがエラーを返した)が派生して、DGraphicsError・DSoundError・DInputErrorの3つがあるのか…。で、それぞれがHRESULT*1を引数にとって、FAILEDならその例外を投げる関数を持ってるのね。

HRESULT hr = /*DirectGraphicsの関数*/;
DGraphicsError::check(hr);

class DGraphicsError : public DXError {
  static void check(HRESULT hr)
  {
    if(FAILED(hr)){
      // msgはDXGetErrorStringやDXGetErrorDescriptionから
      // がんばって作るよ
      throw DGraphicsError(msg);
    }
  }
}

しかしこれ3つの例外に同じようなコピペしてて無駄じゃね?

template <class T>
class DXError : public Dx9libError {
public:
  explicit DXError(const tstring &msg) : Dx9libError(msg) {}
  static void check(HRESULT hr)
  {
    if(FAILED(hr)){
      array<TCHAR, 1024> buf;
      const TCHAR *str = ::DXGetErrorString(hr);
      const TCHAR *desc = ::DXGetErrorDescription(hr);
      ::wsprintf(&buf[0], "%s: %s", str, desc);
      throw T(&buf[0]);
    }
  }
};

アッー

// ChildがParent<Child>を継承するのはよくあること
class DGraphicsError : public DXError<DGraphicsError> {
public:
  explicit DGraphicsError(const tstring &msg) : DXError(msg) {}
};
// 以下あと2つ

アッー
かくして何か月か前の私の「DirectXのエラーは多分この3つから増えることはないから変なこと*2はせずコピペ3つでいいや」という大人の対応は無駄に終わったのであった。書いた時のことよく覚えてないけど。
こういうprivateメンバにアクセスする必要がある場合でもなければstaticメンバ関数じゃなくてクラス外の普通の関数にしろって誰かが言ってただろ…*3

template <class T>
inline void check(HRESULT hr)
{
  if (FAILED(hr)) {
    throw T(...);
  }
}

これでいいじゃないか…。が、放置(爆笑)。
以上のコピペから話のネタができたので…。


tstringについて
string/wstringのtypedefです。

namespace dx9lib {
	using std::array;
	using std::shared_ptr;

	typedef std::basic_string<TCHAR> tstring;
	typedef std::basic_istream<TCHAR> tistream;
	typedef std::basic_ifstream<TCHAR> tifstream;
	typedef std::basic_ofstream<TCHAR> tofstream;
	typedef std::basic_stringstream<TCHAR> tstringstream;
}

(本当に)最初、もうWCHAR直書きでいいんじゃね?と書いていたら、Luaがchar安定とか言い出して、ものすごい勢いでめんどくさくなったのでライブラリの方をTCHARで一般化して、C++の標準ライブラリとかUNICODEマクロでのスイッチがない(よね?)ので上のように自作して、必死で文字列系の部分を全部書き直したんだが、本当にどうしよう。今はUNICODEマクロで全体がスイッチできるようになってて、charでコンパイルしているが…。
LuaコードをUTF-8で書くことにするとShift_JISで「表」とかの文字が化ける問題*4を回避できるらしいし、なんかUTF-8だと分かってる気がするし(ただ、Y教授はUTF-8を非難していた*5 )、printとかの標準関数に渡すと化ける(そりゃ中身がprintfとかだからな)というデメリットも出力関数を自作すればおしまいだしな…。今exeもLuaShift_JISになっているが、exeをUTF-16LuaUTF-8にするかもしれない。
なお、動かない黄金夢想曲のバイナリを参考にしました。UTF-16でした。バイナリエディタで開いてShiftJISとUnicode*6を切り換えて適当にダイアログに表示されている文字列リソースを検索するだけ。


std::arrayとstd::shared_ptrはもう予約語か何かの勢いでusing。一応名前空間に入れてるけど。make_sharedも入れておこう。「配列は全部vectorに」っていう方針はまあ悪くはないが、本当にいつでもどこでも全員にっていうのは無理があると思っていたが、配列はなんでもかんでも全部std::arrayにっていうのは結構やってもいいように思う。

  • size()で要素数が得られる。
  • begin() end() rbegin() rend()があってアルゴリズムへの範囲指定がスマート。
  • assign()やfill()で埋め尽くしができる。
  • {a, b, c}的な初期化子が普通に使える。
  • data()で先頭ポインタ取得可能。外部ライブラリとの連携も大丈夫。

個人的にはsize()が一番うれしい。Cのsizeof除算マクロとかゴミだし、C++でもサイズ数定数宣言すると1行増えるんだよ(爆笑)。


自己言及的な、再帰的な、テンプレートについて

public abstract class Enum<E extends Enum<E>>

信じられるか?これJavaのクラス宣言なんだぜ?断じてテンプレートではないが。
そういやC++のタイプセーフenumってどうなったんだろ…。


Luaで全部書くっていうのはこのbasukeとか書かれてるよくわかんないプロジェクトでやったから、今度はtoLua++でも試してみようかと思ってる。

*1:成功・失敗を表すコード。まあ32bit数なんだが。

*2:主にテンプレート。

*3:多分Effective C++

*4:2バイト目がバックスラッシュと同一なので、字句解析器にダブルクォーテーションの中で1バイトずつバックスラッシュによるエスケープがないか見られると誤動作するPerlによくあった気がする問題。

*5:英数字がアスキーコードそのままで1バイト、日本語が3バイトになる、欧米圏偏重のものだとする考え。その通りだが。

*6:MS用語だとUnicodeUTF-16でマルチバイト文字セットにUTF-8が含まれたりしてるんだよな。危ないからこの使い方は避けてるが…。