【C++/Win32】UTF-16とUTF-8を相互に変換する


更新

 Windows環境にて、C++からWin32 APIを呼び出して、テキストの文字コードをUTF-16とUTF-8の間で変換する方法を説明します。

開発環境Visual Studio 2022
言語C++
フレームワークWin32 API
動作確認Windows 11
Windows 10
Windows 7

 C/C++を使用したWindowsプログラミングでは、通常、テキストの文字コードはUTF-16です。それに対して、オープンソースの便利なライブラリの多くは、WindowsやUNIX系OS等の複数のプラットフォームに対応するため、関数の引数や戻り値はUTF-8です。そのため、両者の変換が必要になります。

 標準C++では、変換用のヘッダーとして<codecvt>が存在しますが、C++17にて非推奨になりました。よって、Win32 APIを使用する方法を以下に示します。

UTF-16からUTF-8


#include <windows.h>


    // 厳密な方法

    wchar_t utf16str[] = L"変換前のテキスト";

    // 変換後のchar配列の要素数(null終端を含む)を取得
    int size = WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, nullptr, 0, nullptr, nullptr);

    char *utf8str = new char[size];

    // 変換
    WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, utf8str, size, nullptr, nullptr);

    // ここで、変換後のテキストが入ったutf8strを使用

    // 解放
    delete[] utf8str;


    // 簡易な方法

    wchar_t utf16str[] = L"変換前のテキスト";

    // 適当に大きい要素数で宣言する
    char utf8str[100]{};

    // 変換
    WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, utf8str, 100, nullptr, nullptr);

    // 解放は不要


#include <windows.h>
#include <string>


    // std::stringに格納する方法

    wchar_t utf16str[] = L"変換前のテキスト";

    // 変換後のchar配列の要素数(null終端を含む)を取得
    int size = WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, nullptr, 0, nullptr, nullptr);

    // null終端を含まない要素数で宣言する
    std::string utf8str(size - 1, '\0');

    // 変換
    WideCharToMultiByte(CP_UTF8, 0, utf16str, -1, &utf8str[0], size - 1, nullptr, nullptr);

    // 解放は不要

UTF-8からUTF-16


#include <windows.h>


    // 厳密な方法

    char utf8str[] = u8"変換前のテキスト";

    // 変換後のwchar_t配列の要素数(null終端を含む)を取得
    int size = MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, nullptr, 0);

    wchar_t *utf16str = new wchar_t[size];

    // 変換
    MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, utf16str, size);

    // ここで、変換後のテキストが入ったutf16strを使用

    // 解放
    delete[] utf16str;


    // 簡易な方法

    char utf8str[] = u8"変換前のテキスト";

    // 適当に大きい要素数で宣言する
    wchar_t utf16str[100]{};

    // 変換
    MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, utf16str, 100);

    // 解放は不要


#include <windows.h>
#include <string>


    // std::wstringに格納する方法

    char utf8str[] = u8"変換前のテキスト";

    // 変換後のwchar_t配列の要素数(null終端を含む)を取得
    int size = MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, nullptr, 0);

    // null終端を含まない要素数で宣言する
    std::wstring utf16str(size - 1, L'\0');

    // 変換
    MultiByteToWideChar(CP_UTF8, 0, utf8str, -1, &utf16str[0], size - 1);

    // 解放は不要

ファイルのパスの場合

 パスをUTF-16からUTF-8やShift_JISに変換するには、<filesystem>を使用するのが簡単です。プロジェクトのプロパティにて、[全般]-[C++言語標準]に「ISO C++17標準(/std:c++17)」以降を選択します。


#include <filesystem>


    std::filesystem::path p = L"変換前のファイルのパス";

    // UTF-8に変換
    //p.u8string().c_str()

    // Shift_JISに変換
    //p.string().c_str()

 UTF-8に対応したライブラリ関数でも、日本語を含んだパスを渡すとファイルのオープンに失敗することがあります。その場合は、Shift_JISに変換して渡してみましょう。

 きちんとした関数は、内部処理にて「#ifdef _WIN32」して、引数のUTF-8をUTF-16に変換して、_wfopen_sやCreateFileに渡します。そうでない関数は、引数をそのままfopenに渡します。後者の場合、とりあえずはShift_JISで渡すと問題無いです。そして理想を言えば、前者のようにソース修正して、オープンソースのプロジェクトに貢献できたら素敵だと思います。

リファレンス

WideCharToMultiByte function
https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-widechartomultibyte

MultiByteToWideChar function
https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar