メモの日々


2023年06月20日(火) [長年日記]

[c++][windows] Visual C++でコードページとlocaleにUTF-8を指定した時の動作

VC++でもマニフェストファイルを用意することでマルチバイト文字列にShife-JISではなくUTF-8を使うことが自然にできるようになる。

また、localeにもUTF-8を指定できるようになっており、これにより動作が変わるようだ。

これらの設定により動作がどのように変わるのかを次のコードで試してみた。

#include <clocale>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>

std::string dump(const char* s)
{
    if (s == nullptr) return "";

    std::ostringstream oss;
    oss << std::hex << std::setfill('0');
    while (*s != '\0') {
        oss << std::setw(2)
            << static_cast<int>(static_cast<std::uint8_t>(*s))
            << ' ';
        ++s;
    }
    return oss.str();
}

int main(int argc, char* argv[])
{
    //std::setlocale(LC_ALL, ".UTF8");

    if (argc <= 1) {
        std::cerr << "too few arguments" << std::endl;
        std::exit(2);
    }

    const char* message = argv[1];

    std::cout << "raw: " << message << std::endl;
    std::cout << "bytes: " << dump(message) << std::endl;

    std::filesystem::path path{message};
    std::filesystem::create_directory(path);

    std::filesystem::current_path(path);
    std::ofstream ofs{path};
    ofs << message;
}
  • locale設定ははじめはコメントアウトしておく。
  • コマンドラインから文字列を受け付けてそれを表示している。
  • 受け付けた文字列でディレクトリとファイルを作成し、そのファイル内に受け付けた文字列を出力している。

以下では、実行する際に「」をパラメータとして指定することにする。「あ」のバイト列は、UTF-8だと「e3 81 82」、Shift-JISだと「82 a0」である。

デフォルトの動作

特に何も設定せずにビルドして試すと、次のように出力される。

raw: あ
bytes: 82 a0

入力した文字列はShift-JISとして読み込まれている。ファイルにもShift-JISで出力された。

ただし、コマンドプロンプトのコードページをUTF-8に変更する(chcp 65001)と、rawの行には何も表示されなくなる。上記の結果はコードページがShift-JISの場合(chcp 932)である。

マニフェストファイルを用意した場合の動作

VC++のプロジェクトの「リソースファイル」フィルタ配下に次の内容のファイルを拡張子「.manifest」で追加してからビルドすると、振る舞いが変わる。なお、assemblyIdentityのnameが「...」になっていることに注意。このままでも動作確認には支障がないようだ。(追記:マニフェストファイルの説明によると、nameとversionにはアプリケーションの名前とバージョンを付与しなければならないようだ)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity type="win32" name="..." version="6.0.0.0"/>
  <application>
    <windowsSettings>
      <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
    </windowsSettings>
  </application>
</assembly>

こうした場合は、コードページがShift-JISのコンソールでは次のように出力される。

raw: 縺
bytes: e3 81 82

バイト列はUTF-8に変わっているが、rawの行は文字化けする。コードページをUTF-8に変更すると文字化けせず表示される。

ファイルに出力される文字のエンコーディングもUTF-8に変わる。

更にlocaleにUTF-8を指定した場合の動作

マニフェストファイルがある状態で、始めに示したコードでコメントアウトしていた

    std::setlocale(LC_ALL, ".UTF8");

の行を生かすように変更すると、コンソールのコードページがShift-JISとUTF-8のどちらでも

raw: あ
bytes: e3 81 82

と表示されるようになった。ファイル出力も問題ない。

アクティブコードページの確認

プロセスのアクティブコードページが何になっているのかはGetACP()を呼び出すことで確認できるようだ。説明には

オペレーティング システムの現在の Windows ANSI コード ページ識別子を取得します。

とあるけれど、プロセスごとのアクティブコードページを返してくれているように見える。