やってみる

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

UuidToStringをUniccodeとマルチバイト文字の両方に対応してみた

私が以前つくったコードのうち、Uuidライブラリを利用している部分がある。 じつはあれ、マルチバイト文字セットを使う設定にするとエラーになる。 今回は対応版を作ってみた。

入手先

GitHub MEGA

問題

以前、作成したWndProcのclass化フレームワークで、Uuidライブラリを使用していた。 Uuidを生成してWindowのクラス名にしていた。

UuidはRpcrt4.lib内にある。 でもじつはあの実装だと、文字セットをマルチバイトにして実行するとエラーになる。

MBCSなんて使わないからいいよね?

文字セットはUnicodeしか使わないから別にかまわないはず。 MSDNでも、MBCS(マルチバイト文字セット)は古い技術だからお勧めしない的なことが書いてあった。

あ、Microsoftのサイトは絶対リンク切れするだろうから引用しておこう。

マルチバイト文字セット (MBCS: Multibyte Character Set) は、日本語や中国語など、1 バイト文字では表現できない文字セットをサポートするニーズに対する古いアプローチです。 新規開発を行う場合は、エンド ユーザーに対して表示されることのないシステム文字列を除き、すべてのテキスト文字列で Unicode 文字列を使用する必要があります。 MBCS は、レガシ テクノロジであり、新規開発にはお勧めしません。

以前から気づいてはいた。でもUuidは本筋ではないし面倒だったので、これまで無視してきた。

いいわけない

実際は使わなくとも、MBCSにできてしまうから問題になる。

物理的に不可能ならともかく、MBCSにできてしまう。 現に、Windowsプログラミングでは、TCHAR定義によって、charwchar_tを区別している。

できてしまう以上、意識せざるを得ない。 お勧めでないとしても、できてしまうなら、できないようにしない限り、罠や面倒事、悪しき風習として注意しておかねばならないということだ。

C++はこういう罠が多すぎる。 少しでも楽をするため、ここで懸念を断ち切りたい。

なんでエラーになる?

MBCSのとき、UuidToString関数を使うと、RPC_CSTR(unsigned char*)型で返却される。 ここを考慮しておらず、RPC_WSTR(wchar_t)で決め打ちしていた。

uuidの型 対応する標準の型
RPC_WSTR wchar_t
RPC_CSTR unsigned char

両方に対応するために、文字セットがUnicodeのときはRPC_WSTR、マルチバイトのときはRPC_CSTRを使うようにする必要がある。

また、TCHARRPC_CSTRの整合性がとれていないため、キャストする必要がある。

Unicode MBCS
uuidの型 wchar_t unsigned char
TCHARの型 wchar_t char

ようは、MBCSのとき、uuidの型とTCHARの型が一致しない。 Unicodeのときはどちらもwchar_tで問題ないが、マルチバイトのときはunsignedの有無が違う。

この辺を解決してやれば、UnicodeでもMBCSでも両方に対応したコードになる。

参考

http://d.hatena.ne.jp/ir9Ex/20061018/1161171674

ソースコード

修正前と後のソースコード抜粋。

  • 文字セット対応
  • 例外をthrowする
  • キャストをC++の方式に変更

Before

basic_string<TCHAR> GetUuid()
{
    RPC_WSTR p;
    UUID uuid;

    ::UuidCreate(&uuid);
    ::UuidToString(&uuid, &p);

    basic_string<TCHAR> str = (WCHAR*)p;
    ::RpcStringFree(&p);

    return str;
}

After

std::basic_string<TCHAR> GetUuid()
{
    // 文字セットにより型を変更する
    #if defined(_UNICODE) || defined(UNICODE)
    RPC_WSTR p;
    #else
    RPC_CSTR p;
    #endif

    UUID uuid;
    RPC_STATUS res;

    // UUID生成
    res = ::UuidCreate(&uuid);
    if (res == RPC_S_OK) {}
    else if (res == RPC_S_UUID_LOCAL_ONLY) { throw "::UuidCreate関数失敗。RPC_S_UUID_LOCAL_ONLY。"; }
    else if (res == RPC_S_UUID_NO_ADDRESS) { throw "::UuidCreate関数失敗。RPC_S_UUID_NO_ADDRESS。"; }
    else {}

    // UUIDを文字列にする
    res = ::UuidToString(&uuid, &p);
    if (res == RPC_S_OK) {}
    else if (res == RPC_S_OUT_OF_MEMORY) { throw "::UuidToString関数失敗。RPC_S_OUT_OF_MEMORY。"; }
    else {}

    // 文字セットにより型をキャストする
    // とくにMBCSの場合、RPC_CSTRはunsigned char*である。
    // MBCSの場合、TCHARはcharになるため、char型にキャストする必要がある。
    #if defined(_UNICODE) || defined(UNICODE)
    std::basic_string<TCHAR> str = reinterpret_cast<WCHAR*>(p);
    #else
    std::basic_string<TCHAR> str = reinterpret_cast<CHAR*>(p);
    #endif

    ::RpcStringFree(&p);

    return str;
}

所感

気になっていたことができてすっきりした。