私が以前つくったコードのうち、Uuidライブラリを利用している部分がある。 じつはあれ、マルチバイト文字セットを使う設定にするとエラーになる。 今回は対応版を作ってみた。
入手先
問題
以前、作成した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
定義によって、char
とwchar_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
を使うようにする必要がある。
また、TCHAR
とRPC_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;
}
所感
気になっていたことができてすっきりした。