メモの日々


2023年06月19日(月) [長年日記]

[c++][windows] Visual C++でソースコードのエンコーディングをUTF-8にするときの注意

UTF-8を使って書かれたソースコードをVC++でコンパイルすることは、正しい設定をしていれば問題ない。ただ、正しく設定されていないと分かりにくい問題が起こることがあるのでメモ。

問題のある挙動

UTF-8で保存した次のコードはVC++でコンパイルできる。

#include <iostream>

int main()
{
    int a = 0;

    // ああ
    a = 100;

    // あ
    a = 200;

    std::cout << "a=" << a << std::endl;
}

しかし、条件が揃っていると、実行したときに

a=100

と出力される。

不思議だし、エラーになるわけではないのでこのような問題が起きていることに気づかないケースもあるだろう。

問題が起こる条件

上記のようになるのは、次の条件が揃っているときだ。

  • ソースコードの文字エンコーディングがUTF-8
  • ソースコードにBOMが無い
  • ソースコードの改行コードがLF
  • コンパイルオプションで文字エンコーディングを指定していない

自分が遭遇したのは、問題なかったコードに対してビルドできないという報告が上がってきたこと。原因は、それまではgitの設定が core.autocrlf=true な環境で使っていたのを core.autocrlf=false な環境でビルドしたため、改行コードがLFに変わったことだった。

対策

上述の条件のどれかを変えればいいのだけれど、通常は、コンパイルオプションで文字エンコーディングを指定するのがいいだろう。

具体的には、/utf-8を指定するようにすればよい。


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 コード ページ識別子を取得します。

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


2023年06月23日(金) [長年日記]

[windows][c++] Pythonから呼び出すDLLをVisual Studioのデバッガでデバッグする

pybind11を使ってC++で書かれたPython用のDLLのコードを、Visual Studio 2019のデバッガで開きたかった。

Visual Studioの [デバッグ] → [プロセスにアタッチ] で開くダイアログからPythonのプロセスにアタッチすればいいだろうと考えたが、DLLのコードにブレークポイントを設定してもそこで止まってくれない。

原因は、アタッチした時にPython用のデバッガが開いてしまっているからのようだ。「プロセスにアタッチ」ダイアログには「アタッチ先」という欄があって「自動」が選ばれていたが、「選択」ボタンで開く「コードの種類の選択」ダイアログで「ネイティブ」を選択したらDLLのコードのブレークポイントで止まるようになった。

Python用のデバッガが開いてしまうのはVisual Studioに「Python開発」のワークロードをインストールしているからな気がする。これが無い環境なら何も設定しなくてもC++用のデバッガが開くのかもしれない。

プロセスにアタッチダイアログ


2023年06月24日(土) [長年日記]

[c++][windows] VC++でstd::filesystem::filesystem_errorのエラーメッセージを英語にする

次のコードをVC++でコンパイルして実行すると、エラーメッセージが日本語で出力される。

#include <filesystem>
#include <iostream>

int main()
{
    try {
        std::filesystem::copy_file("あ.txt", "ああ.txt");
    } catch (const std::filesystem::filesystem_error& e) {
        std::cout << e.what() << std::endl;
    }
}
copy_file: 指定されたファイルが見つかりません。: "あ.txt", "ああ.txt"

これを英語で出力されるようにするにはどうしたらいいのか?(OSの言語設定は日本語のままで)

    std::setlocale(LC_ALL, "C");

を追加したらいいかなと思ったけど、こうしてもメッセージは日本語のままだった。

おまけ:FormatMessage関数のdwLanguageId引数=0で実装されているため、SetThreadUILanguage関数でスレッドのLANGIDを事前に変更しておく案もある。シングルスレッドプログラムならこれでもいいか...

とあるのを発見し、これを試すと英語メッセージに変わった。こうするしかない?

#include <filesystem>
#include <iostream>
#include <windows.h>

int main()
{
	SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
	try {
		std::filesystem::copy_file("あ.txt", "ああ.txt");
	} catch (const std::filesystem::filesystem_error& e) {
		std::cout << e.what() << std::endl;
	}
}
copy_file: The system cannot find the file specified.: "あ.txt", "ああ.txt"