こんにちは、Sayahamittです。
Macで書いていたC++コードをWindows VC++に持ってきたら文字コードでハマったのでメモしておきます。
今回は引数にUTF8エンコードのstd::string型文字列を取り、ShiftJISエンコードのstd::string文字列を返す関数と、その逆をやる関数を作りました。
バリバリのWindows API依存コードです。ごめんなさいm(__)m
Windowsでは内部的な処理にはUnicodeが使われているにも関わらず、コンソールにおける入出力を始めAPIなど、ユーザーとのインターフェースには未だにShift-JISが使われているようで、UTF8など多バイト文字を直接利用出来ません。
WindowsAPIなんて殆ど触ったことがない自分も、もれなくこの問題に引っかかりました。
Win API を利用する場合にはMultiByteToWideCharメソッドとWideCharToMultiByteメソッドを用いてUTF8とShiftJISの相互変換が実現できるようです。
まず、上記の両メソッドを利用する場合は
1.一度変換元文字列を1バイト文字の文字列として読み込んで、それをUnicodeな多バイト文字型に変換
2.Unicodeな多バイト文字列をShiftJISに変換
という手順が必要なようです。
自分は当初、変換表のようなものを使って一発で変換できると思っていたので少し戸惑いました。
特に変換元がUTF8場合に、なんでMultiByteToWideCharを先に使うのか理解できなくて大分悩んでしまった…(´・ω・`)
何はともあれ、大事なのはコードですコード。
MultiByteToWideCharメソッドとWideCharToMultiByteメソッドの詳しい説明はMSDNのページを参照して下さい。(自分にはチンプンカンプンで何も分からん)
以下、取り敢えず動いたコード。
UTF-8 → Shift-JIS 変換
#include <Windows.h> std::string UTF8toSjis(std::string srcUTF8){ //Unicodeへ変換後の文字列長を得る int lenghtUnicode = MultiByteToWideChar(CP_UTF8, 0, srcUTF8.c_str(),srcUTF8.size() + 1, NULL, 0); //必要な分だけUnicode文字列のバッファを確保 wchar_t* bufUnicode = new wchar_t[lenghtUnicode]; //UTF8からUnicodeへ変換 MultiByteToWideChar(CP_UTF8, 0, srcUTF8.c_str(), srcUTF8.size() + 1,bufUnicode, lenghtUnicode); //ShiftJISへ変換後の文字列長を得る int lengthSJis = WideCharToMultiByte(CP_THREAD_ACP, 0, bufUnicode, -1, NULL, 0, NULL, NULL); //必要な分だけShiftJIS文字列のバッファを確保 char* bufShiftJis = new char[lengthSJis]; //UnicodeからShiftJISへ変換 WideCharToMultiByte(CP_THREAD_ACP, 0, bufUnicode, lenghtUnicode + 1, bufShiftJis, lengthSJis, NULL, NULL); std::string strSJis(bufShiftJis); delete bufUnicode; delete bufShiftJis; return strSJis; }
Shift-JIS → UTF-8 変換
#include <Windows.h> std::string SjistoUTF8(std::string srcSjis){ //Unicodeへ変換後の文字列長を得る int lenghtUnicode = MultiByteToWideChar(CP_THREAD_ACP, 0, srcSjis.c_str(), srcSjis.size() + 1, NULL, 0); //必要な分だけUnicode文字列のバッファを確保 wchar_t* bufUnicode = new wchar_t[lenghtUnicode]; //ShiftJISからUnicodeへ変換 MultiByteToWideChar(CP_THREAD_ACP, 0, srcSjis.c_str(), srcSjis.size() + 1, bufUnicode, lenghtUnicode); //UTF8へ変換後の文字列長を得る int lengthUTF8 = WideCharToMultiByte(CP_UTF8, 0, bufUnicode, -1, NULL, 0, NULL, NULL); //必要な分だけUTF8文字列のバッファを確保 char* bufUTF8 = new char[lengthUTF8]; //UnicodeからUTF8へ変換 WideCharToMultiByte(CP_UTF8, 0, bufUnicode, lenghtUnicode + 1, bufUTF8, lengthUTF8, NULL, NULL); std::string strUTF8(bufUTF8); delete bufUnicode; delete bufUTF8; return strUTF8; }
相互変換の両者の違いは、UTF8に関連する文字列とShiftJISに関連する文字列が丸っと全部入れ替わるだけです。
上記のコードは以下のサイトを丸パクr参考にさせて頂きました。
エンコードの変更につまずいていたので助かりました。
ただ、memory leakがあります:
delete bufUnicode;
delete bufUTF8;
一番最初の char 一個分しかfreeしてません。
safer:
delete [] bufUnicode;
====
あと、問題は起きてないみたいですが:
WideCharToMultiByte(CP_UTF8, 0, bufUnicode, lenghtUnicode + 1, bufUTF8, lengthUTF8, NULL, NULL);
lenghtUnicode + 1はbufUnicodenobufferのsizeを超えています。
-1かlengthUnicodeが(仕様書が正しければ)お勧めです。
まあ、lengthUTF8が書き込みに対するガードをしてるかが読み込みを止めてるようなので、問題無いようですが。
デストラクタを呼ばないので [] 無しでも同じではないですか。
末尾に文字化けが出るのですが、
//必要な分だけUTF8文字列のバッファを確保
char* bufUTF8 = new char[lengthUTF8];
memset(bufUTF8,0,lengthUTF8);
みたいな感じにしたら文字化けが無くなりました。