C++と色々

主にC++やプログラムに関する記事を投稿します。

Visual C++における文字コード変換

前提環境

日本語版WindowsかつVisual C++の環境の話です*1Windows以外の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_tchar16_t はお互いに代入することが出来ません。

string(Shift_JIS)とwstringの文字コード変換

string(Shift_JIS)とwstringの変換にはC標準ライブラリを使って変換する方法とWindows API使う方法があります。

C標準ライブラリ

C標準ライブラリには mbstowcswcstombs というstringとwstringを変換する関数がありますが、Visual C++にはロケールを指定できる _mbstowcs_l_wcstombs_l 、それらのセキュア版の mbstowcs_s、_mbstowcs_s_lwcstombs_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());
}

mbstowcs_s、_mbstowcs_s_l

  • 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());
}

wcstombs_s、_wcstombs_s_l

Windows API

WinAPIに MultiByteToWideCharWideCharToMultiByte があります。コードページには CP_ACP を指定します。 CP_ACPANSIコードページという意味ですが、なぜか日本語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());
}

MultiByteToWideChar 関数

  • 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());
}

WideCharToMultiByte 関数

string(UTF-8)とwstringの文字コード変換

C++標準ライブラリとWindows API使う方法があります。

C++標準ライブラリ

std::codecvtstd::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

先程登場した MultiByteToWideCharWideCharToMultiByte のコードページを 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-16UTF-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

visual studio - Linker error using VS 2015 RC, can't find symbol related to std::codecvt - Stack Overflow

まとめ

ここまで書いたコードのまとめはこちらになります

gistf834549ffd30311a19296f8d5b4cc6e8

*1:特にWindowsのバージョンとVisual C++のバージョンを明示していませんが、よほど古くない限り当てはまると思います

*2:トルエンディアン

*3:http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1490895514