« Greasemetal (Google Chrome 用 Greasemonkey) を公開しました | メイン | Greasemetal 0.2 をリリースしました »

2008年09月12日

C++ テンプレートを使って MBCS と Unicode ちゃんぽんなコードを書く話

 あちこちから寄せ集めたコードを組み合わせるようなプログラムを書いていると (Greasemetal のことですね)、プログラム内に MBCS 前提のコードと UTF16 前提のコードが混在することが往々にして発生します。

 C++ の世界のみで完結できるのであれば、型テンプレートと関数の多重定義を使って総称的なコードを簡単に書けるのですが、実際は、MBCS 版と UTF16 版で関数の名称が異なる C API を呼び出す必要が出てきたりします。具体的には、RegQueryValueExA と RegQueryValueExW を (あるいは fgets と fgetws を)、自動的に呼び分ける総称的なコードが書きたい、といったケースになります。

 で、うーん、と思った結果、以下のようなコードを書いてみました。

template <typename AFUNC, typename WFUNC>
struct AnyCST {
  AFUNC* a_;
  WFUNC* w_;
  AnyCST(AFUNC* a, WFUNC* w) : a_(a), w_(w) {}
  AFUNC* operator()(const char*) const { return a_; }
  WFUNC* operator()(const wchar_t*) const { return w_; }
};

template <typename AFUNC, typename WFUNC>
AnyCST<AFUNC, WFUNC> AnyCS(AFUNC* a, WFUNC* w)
{
  return AnyCST<AFUNC, WFUNC>(a, w);
}

#define ANYCS(func, type) (AnyCS(func##A,func##W)((type*)NULL))
#define ANYCS2(funcA, funcW, type) (AnyCS(funcA,funcW)((type*)NULL))

 このラッパーを使うと、たとえば RegQueryValueEx を呼び出した後に ExpandEnvironmentStrings を呼ぶようなユーティリティ関数の MBCS 版と Unicode 版を、C++ テンプレートを使って総称的に書くことができます。

template <typename T>
std::basic_string<T> GetRegValue(HKEY key, const std::basic_string name)
{
...
  if (ANYCS(RegQueryValueEx, T)(
        key, name.c_str(), &type, reinterpret_cast(buf), &bufsz)
      != ERROR_SUCCESS) {
    return _T("");
  }
  switch (type) {
  case REG_EXPAND_SZ:
    expLen = ANYCS(ExpandEnvironmentStrings, T)(
      buf, expbuf, sizeof(expbuf) / sizeof(expbuf[0]));
...

// mbcs なコード内で利用
string mbcsValue = GetRegValue(HKCU, "hogehoge");

// unicode なコード内で利用
wstring unicodeValue = GetRegValue(HKCU, L"hogehoge");

 fgets と fgetws を呼び分ける場合はこんな感じ。

  T buf[BUFSZ];
  if (ANYCS2(fgets, fgetws, T)(buf, BUFSZ, fp) != NULL) {
...

 まああまりやりすぎると、ロケール依存の MBCS と utf8 と utf16 が混在する悲惨なプログラムになっちゃったりするかもしれなかったり注意が必要だとは思いますが... 書いてみたという話。

投稿者 kazuho : 2008年09月12日 10:22 このエントリーを含むはてなブックマーク このエントリーを含むはてなブックマーク

トラックバック

このエントリーのトラックバックURL:
http://labs.cybozu.co.jp/cgi-bin/mt-admin/mt-tbp.cgi/2008