Visual C++における文字コード変換
前提環境
日本語版WindowsかつVisual C++の環境の話です*1。Windows以外のOSや非日本語のWindows、Visual C++以外のコンパイラは当てはまりません。
用語
ここでは char const*
の文字列や char
の文字 std::string
で表現されるマルチバイト文字(列)をまとめて string
と表現します。また、 wchar_t const*
の文字列 wchar_t
の文字、 std::wstring
で表現されるワイド文字(列)をまとめて wstring
と表現します。
文字列と文字を区別すると文章が長くなるため一律文字と表現します。
内部表現
前述の環境において、stringの内部表現にはShift_JISが使われています。また C++11で入ったUTF-8リテラルの型にもstringが使われています。つまりstringにはShift_JISの文字かUTF-8の文字が入ってる可能性があります。
char const* sjis = "あ"; char const* utf8 = u8"あ";
wstringは内部表現はUTF-16LE*2が使われています。C++11ではUTF-16のリテラルとそれを表現するchar16_t型が追加されました。内部表現が同じとはいえ型が違うため、当然ですが wchar_t
と char16_t
はお互いに代入することが出来ません。
string(Shift_JIS)とwstringの文字コード変換
string(Shift_JIS)とwstringの変換にはC標準ライブラリを使って変換する方法とWindows API使う方法があります。
C標準ライブラリ
C標準ライブラリには mbstowcs
と wcstombs
というstringとwstringを変換する関数がありますが、Visual C++にはロケールを指定できる _mbstowcs_l
と _wcstombs_l
、それらのセキュア版の mbstowcs_s、_mbstowcs_s_l
と wcstombs_s、_wcstombs_s_l
があります。なので後者を使っていきます。ロケールを日本語にしないと変換に失敗するため、あらかじめ setlocale
を呼ぶか _l
版を呼ぶ必要があります。
- string -> wstring
std::wstring multi_to_wide_capi(std::string const& src) { std::size_t converted{}; std::vector<wchar_t> dest(src.size(), L'\0'); if (::_mbstowcs_s_l(&converted, dest.data(), dest.size(), src.data(), _TRUNCATE, ::_create_locale(LC_ALL, "jpn")) != 0) { throw std::system_error{errno, std::system_category()}; } dest.resize(std::char_traits<wchar_t>::length(dest.data())); dest.shrink_to_fit(); return std::wstring(dest.begin(), dest.end()); }
- wstring -> string
std::string wide_to_multi_capi(std::wstring const& src) { std::size_t converted{}; std::vector<char> dest(src.size() * sizeof(wchar_t) + 1 , '\0'); if (::_wcstombs_s_l(&converted, dest.data(), dest.size(), src.data(), _TRUNCATE, ::_create_locale(LC_ALL, "jpn")) != 0) { throw std::system_error{errno, std::system_category()}; } dest.resize(std::char_traits<char>::length(dest.data())); dest.shrink_to_fit(); return std::string(dest.begin(), dest.end()); }
Windows API
WinAPIに MultiByteToWideChar
と WideCharToMultiByte
があります。コードページには CP_ACP
を指定します。 CP_ACP
はANSIコードページという意味ですが、なぜか日本語Windowsでは独自拡張したShift_JISのことをANSIと呼んでいます*3。これらの関数の出力バッファのサイズを渡す引数に0を渡すと出力バッファに必要なバイト数が戻り値で返るため、最初に0で呼び出して長さを取得してから、変換を行います。
- string -> wstring
std::wstring multi_to_wide_winapi(std::string const& src) { auto const dest_size = ::MultiByteToWideChar(CP_ACP, 0U, src.data(), -1, nullptr, 0U); std::vector<wchar_t> dest(dest_size, L'\0'); if (::MultiByteToWideChar(CP_ACP, 0U, src.data(), -1, dest.data(), dest.size()) == 0) { throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()}; } dest.resize(std::char_traits<wchar_t>::length(dest.data())); dest.shrink_to_fit(); return std::wstring(dest.begin(), dest.end()); }
- wstring -> string
std::string wide_to_multi_winapi(std::wstring const& src) { auto const dest_size = ::WideCharToMultiByte(CP_ACP, 0U, src.data(), -1, nullptr, 0, nullptr, nullptr); std::vector<char> dest(dest_size, '\0'); if (::WideCharToMultiByte(CP_ACP, 0U, src.data(), -1, dest.data(), dest.size(), nullptr, nullptr) == 0) { throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()}; } dest.resize(std::char_traits<char>::length(dest.data())); dest.shrink_to_fit(); return std::string(dest.begin(), dest.end()); }
string(UTF-8)とwstringの文字コード変換
C++標準ライブラリとWindows API使う方法があります。
C++標準ライブラリ
std::codecvt
と std::wstring_convert
を使って変換できます。
- string -> wstring
std::wstring utf8_to_wide_cppapi(std::string const& src) { std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter; return converter.from_bytes(src); }
- wstring -> string
std::string wide_to_utf8_cppapi(std::wstring const& src) { std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter; return converter.to_bytes(src); }
std::codecvt_utf8_utf16 - cppreference.com
Windows API
先程登場した MultiByteToWideChar
と WideCharToMultiByte
のコードページを CP_UTF8
にするだけでOKです。
- string -> wstring
std::wstring utf8_to_wide_winapi(std::string const& src) { auto const dest_size = ::MultiByteToWideChar(CP_UTF8, 0U, src.data(), -1, nullptr, 0U); std::vector<wchar_t> dest(dest_size, L'\0'); if (::MultiByteToWideChar(CP_UTF8, 0U, src.data(), -1, dest.data(), dest.size()) == 0) { throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()}; } dest.resize(std::char_traits<wchar_t>::length(dest.data())); dest.shrink_to_fit(); return std::wstring(dest.begin(), dest.end()); }
- wstring -> string
std::string wide_to_utf8_winapi(std::wstring const& src) { auto const dest_size = ::WideCharToMultiByte(CP_UTF8, 0U, src.data(), -1, nullptr, 0, nullptr, nullptr); std::vector<char> dest(dest_size, '\0'); if (::WideCharToMultiByte(CP_UTF8, 0U, src.data(), -1, dest.data(), dest.size(), nullptr, nullptr) == 0) { throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()}; } dest.resize(std::char_traits<char>::length(dest.data())); dest.shrink_to_fit(); return std::string(dest.begin(), dest.end()); }
string(Shift_JIS)とstring(UTF-8)の文字コード変換
直接変換する方法はWindows APIにもC/C++標準ライブラリにもありませんが、一度wstringを経由することで変換できます。上記で作成した関数を組み合わせることで実装できます。
C/C++標準ライブラリ
std::string multi_to_utf8_cppapi(std::string const& src) { auto const wide = multi_to_wide_capi(src); return wide_to_utf8_cppapi(wide); }
std::string utf8_to_multi_cppapi(std::string const& src) { auto const wide = utf8_to_wide_cppapi(src); return wide_to_multi_capi(wide); }
Windows API
std::string multi_to_utf8_winapi(std::string const& src) { auto const wide = multi_to_wide_winapi(src); return wide_to_utf8_winapi(wide); }
std::string utf8_to_multi_winapi(std::string const& src) { auto const wide = utf8_to_wide_winapi(src); return wide_to_multi_winapi(wide); }
string(UTF-8)とu16stringの文字コード変換
C++11で追加されたUTF-16とUTF-32とstring(UTF-8)の変換は標準で用意されています。しかし以下のコードは Visual C++ 2015 と Visual C++ 2017 RC ではリンカラーになります。
std::u16string utf8_to_utf16(std::string const& src) { std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter; return converter.from_bytes(src); } std::string utf16_to_utf8(std::u16string const& src) { std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter; return converter.to_bytes(src); }
これは不具合で既に報告済になっています
VS 2015 RC linker std::codecvt error
Microsoft Connect is Retired - Collaborate | Microsoft Docs
まとめ
ここまで書いたコードのまとめはこちらになります