Windowsには文字コード変換用のAPI関数としてWideCharToMultiByteやMultiByteToWideCharがある。それぞれユニコード文字列をSHIFT-JISにしたり、SHIFT-JIS文字列をユニコードにしたりするときに用いるためのAPIだ。
前回までにこれらの関数を用いたユニコード<->SHIFT-JISの相互変換関数のソースコードを紹介した。しかし私自身、今までソフト開発をするときにWideCharToMultiByteやMultiByteToWideCharを呼び出して利用した経験はほとんどない。文字列の文字コード変換を要するソフト開発をしたことがないからというわけではない。利用する必要性を感じなかったからだ。
大量の文字列データを扱うソフトの場合や処理スピードを速くしなければならないソフトは別として、ほとんどのソフト開発ではそれほど頻繁かつ大量に文字コードを変換することはない。仮に変換をしても数バイトから数キロバイト程度の小さい文字列をたまに変換する程度のことが多い。
そのようなソフトの場合はchar*やWCHAR*やstrcpy、wcscpyなどを使ってゴリゴリ文字列操作をするよりも、むしろMFCやATL、WTLに備わる文字列操作用クラスCStringを利用している。CStringは非常に文字列の扱いが楽に作られている。例えば...
//変数定義 CStringA strText; //文字列代入 strText = "あいうえお"; //文字列追加 strText += "かきくけこ"; //文字列長取得 int nLen = strText.GetLength();
というように簡単に利用できる。
同じことをchar*を使って表現すると...
//変数定義 char pszText[25]; //文字列代入 strcpy(pszText,"あいうえお"); //文字列追加 strcat(pszText,"かきくけこ"); //文字列長取得 int nLen = strlen(pszText);
というようになる。
「あれ?CStringを使ってもchar*でゴリゴリ書いても差はないじゃん!」と思うかもしれない。そこに重大な落とし穴がある。例えば...
//変数定義 CStringA strText; //文字列代入 strText = "あいうえおああああああああああああああああ"; //文字列追加 strText += "かきくけここここここここここここここここ"; //文字列長取得 int nLen = strText.GetLength();
上のように代入や追加する文字列の長さを大きくとってもCStringではそのまま動く。しかし
//変数定義 char pszText[25]; //文字列代入 strcpy(pszText,"あいうえおああああああああああああああああ"); //文字列追加 strcat(pszText,"かきくけここここここここここここここここ"); //文字列長取得 int nLen = strlen(pszText);
上の場合はpszTextに割り当てられているメモリサイズを超えて代入してしまうので、実行時にエラーになる。CStringは内部で代入や追加される文字列の長さに応じて動的にメモリを確保する仕組みがあるが、ここで書いたchar*を使ったソースコードはメモリを固定長で宣言しているため、それを超える文字列を扱えないためだ。もちろんchar*の場合でも動的に確保するようにプログラミングすればエラーを起こさないようにできる。しかしかなり面倒になる。実装量が増えるのでバグをはらむ可能性も高くなるだろう。そんなわけで文字列操作にはCString系を使うと安全かつ便利だ。
少し話しがずれたが、文字コードの変換にWideCharToMultiByteやMultiByteToWideCharを使わないという理由は、CStringがこれらの変換処理も自動で行うように設計されているからだ。
CStringという型はTCHARと同じように振舞う。「UNICODE」が宣言されているとユニコード文字列を扱うクラスになり、宣言されていないと(日本語の場合は)SHIFT-JIS文字列を扱うクラスになる。
TCHARに対してWCHARやcharがあるように、CStringに対してはCStringWとCStringAが用意されている。
CStringAは非ユニコード文字列を扱うクラス
CStringWはユニコード文字列を扱うクラス
CStringはユニコードビルド時はユニコード文字列、そうでないときは非ユニコード文字列を扱うクラス
というようになる。そして...
//charと同一の型(非ユニコード型)で宣言 CStringA strChar; //SHIFT-JIS文字列を代入 strChar = "あいうえお"; //ユニコード文字列を代入 strChar = L"あいうえお";
上のように、非ユニコード型であるCStringAに対してユニコード文字列をそのまま代入することもできる。このときに自動的にCStringAの内部でWideCharToMultiByteが呼び出されてユニコード文字列から非ユニコード文字列に変換されている。
ATL用のCStringの場合はクラス定義の中(cstringt.hとatlstr.hの中)で以下のように実装されている。
CStringT& operator=( __in_z_opt PCYSTR pszSrc ) { // nDestLength is in XCHARs int nDestLength = (pszSrc != NULL) ? StringTraits::GetBaseTypeLength( pszSrc ) : 0; if( nDestLength > 0 ) { PXSTR pszBuffer = GetBuffer( nDestLength ); StringTraits::ConvertToBaseType( pszBuffer, nDestLength, pszSrc); ReleaseBufferSetLength( nDestLength ); } else { Empty(); } return( *this ); } static int GetBaseTypeLength(__in_z const wchar_t* pszSrc) throw() { // Returns required buffer length in XCHARs return ::WideCharToMultiByte(_AtlGetConversionACP(), 0, pszSrc, -1, NULL, 0, NULL, NULL)-1; } static void ConvertToBaseType(__out_ecount(nDestLength) _CharType* pszDest, __in int nDestLength, __in_ecount(nSrcLength) const wchar_t* pszSrc, __in int nSrcLength = -1) throw() { // nLen is in XCHARs ::WideCharToMultiByte(_AtlGetConversionACP(), 0, pszSrc, nSrcLength, pszDest, nDestLength, NULL, NULL); }確かにWideCharToMultiByteが呼ばれている。
どうだろう?これでWideCharToMultiByteやMultiByteToWideCharを使わない理由が分かっただろうか?
MFCやATL、WTLといったMicrosoftが用意するライブラリは必ずしも完璧ではない。少なからずバグがあり思いもよらぬ誤動作が起きて原因追求に手間取ることもある。しかしながら、自分で書いたソースコードよりも品質が高く信頼性が高いことだろう。そのためこれらのライブラリに使いたい処理が実装されているのであれば使った方が実装やテストの開発コストを抑えることができてお得だ。あるものはどんどん利用しよう。