実現したいこと
MINGW64で作ったC++プログラムで正しく日本語を扱いたい
前提
- MSYS2-MINGW64使用
- MSVCのRuntimeを使用する
- libstdc++を使用する
- gccを使用する(現状12.2.0)
- MSYS2などの環境に依存せず、作成したバイナリは単品で動作する
- Windows 10以降はmustで、可能ならそれ以前でも正しく動作させたい
- MSYS2環境のbashで動かす場合、環境変数LC_*/LANG/LANGUAGEは未設定とする(LC_CTYPE=ja_JP.UTF-8は可)
参考
https://www.msys2.org/docs/environments/
質問
MINGW64で日本語を扱うプログラムをC++で正しく記述する方法を教えてください。
何も考えずに実装すると、Hello, worldレベルで正しく動きません。
発生している問題・エラーメッセージ
例を2つほど挙げます
※ソースコードは次節参照
(1) std::coutに出力するケース
ビルドと実行(システムロケールはコードページ932でmsys環境(mintty)で実施)
bash
1$ g++ -g -Wall -static hello.cpp -o hello 2$ env | grep LANG3$ env | grep LC 4LC_CTYPE=ja_JP.UTF-8 5$ ./hello 6縺薙s縺ォ縺。縺ッ 7 8$
(2) std::wcoutに出力するケース
bash
1$ g++ -g -Wall -static hellow.cpp -o hellow 2$ ./hellow 3 4$
該当のソースコード
hello.cpp(utf-8で保存)
C++
1#include <iostream>2int main() {3 std::cout << "こんにちは" << std::endl;4 return 0;5}
hellow.cpp(utf-8で保存)
C++
1#include <iostream>2int main() {3 std::wcout << L"こんにちは" << std::endl;4 return 0;5}
試したこと
(1) hexdumpで調べる
bash
1$ ./hello | hexdump -C 200000000 e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af 0d |................|300000010 0a |.|400000011 5 6$ ./hellow | hexdump -C 7 8$
MSYS2 bashでパイプで出力する場合、./helloはUTF-8で出力されている
MSYS2 bashでパイプで出力する場合、./hellowは何も出力しない
(2) iconvで調べる
bash
1$ ./hello | iconv -f utf-8 -t cp932 2▒▒▒▒ɂ▒▒▒ 3 4$ ./hello | iconv -f utf-8 -t cp932 | hexdump -C 500000000 82 b1 82 f1 82 c9 82 bf 82 cd 0d 0a |............|60000000c 7 8$
iconvでシフトJISに変換出力してみたが、さらに文字化けするだけだった
変換は正しく実施されている
(3) catを噛ませてみる
bash
1$ ./hello | cat2こんにちは 3 4$./hello | cat | hexdump -C 500000000 e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af 0d |................|600000010 0a |.|700000011 8 9$ env | egrep -E '(LC_|LANG)'10LC_CTYPE=ja_JP.UTF-8 11 12$
MSYS2のcatを噛ませると同じ内容でも正しく表示される
(4) cmdから見てみる
cmd
1C:\>hello 2縺薙s縺ォ縺。縺ッ 3 4C:\>hello | C:\msys64\usr\bin\cat 5こんにちは 6 7C:\>chcp 8現在のコード ページ: 932 9 10C:\>set | findstr LC_ 11 12C:\>set | findstr LANG 13 14C:\>
※conhost.exe上のcmd.exe(minttyなどMSYS2環境ではなく、純粋なWindows環境)
コマンドプロンプトからでも(3)の現象は同様に再現する。
(5) 絵文字を出力させてみる
bash
1$ cat emoji.cpp 2#include <iostream>3int main() {4 std::cout << "😊" << std::endl;5 return 0;6}7 8$ g++ -g -Wall -static emoji.cpp -o emoji 9 10$ ./emoji 11・ 12 13$ ./emoji | cat14😊 15 16$ ./emoji | cat | hexdump -C 1700000000 f0 9f 98 8a 0d 0a |......|1800000006 19 20$
絵文字を表示可能なことから、シフトJISで表示しようとしていないことが分かる。
(6) リテラルをシフトJISとしてコンパイルさせる
bash
1$ g++ -g -Wall -static -fexec-charset=CP932 hello.cpp -o hello_sjis 2 3$ ./hello_sjis 4こんにちは 5 6$ ./hello_sjis | hexdump -C 700000000 82 b1 82 f1 82 c9 82 bf 82 cd 0d 0a |............|80000000c 9 10$
シフトJISとして出力されており、正しく表示されている。
(7) 依存DLLを調べる
bash
1$ ldd hello 2 ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc4950000)3 KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc4660000)4 KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc2400000)5 msvcrt.dll => /c/WINDOWS/System32/msvcrt.dll (0x7fffc45c0000)6 7$ ldd `which cat`8 ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc4950000)9 KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc4660000)10 KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc2400000)11 msys-intl-8.dll => /usr/bin/msys-intl-8.dll (0x430b30000)12 msys-2.0.dll => /usr/bin/msys-2.0.dll (0x180040000)13 msys-iconv-2.dll => /usr/bin/msys-iconv-2.dll (0x5603f0000)14 15$ ldd `which bash`16 ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fffc4950000)17 KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7fffc4660000)18 KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7fffc2400000)19 USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7fffc3a60000)20 win32u.dll => /c/WINDOWS/System32/win32u.dll (0x7fffc2970000)21 msys-2.0.dll => /usr/bin/msys-2.0.dll (0x180040000)22 GDI32.dll => /c/WINDOWS/System32/GDI32.dll (0x7fffc32e0000)23 gdi32full.dll => /c/WINDOWS/System32/gdi32full.dll (0x7fffc20c0000)24 msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7fffc2360000)25 ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7fffc2260000)26 27$
catやbashなどはmsys-2.0.dllをリンクしており、MSYS2ランタイムで動作しているのが分かる。
helloはMINGW64プログラムなのでMSYS2ランタイムで動作していない。
以上から標準入出力の繋がり方が、MSYS2ランタイムを使ったもの同士とそれ以外で異なるのではないかと推測している。
パイプの場合はbashとcmdで違うのではないかと推測している。
(8) wcoutの設定
bash
1$ cat hellow2.cpp 2#include <iostream>3int main() {4 std::wcout.imbue(std::locale(""));5 std::wcout << L"こんにちは" << std::endl;6 return 0;7}8 9$ g++ -g -Wall -static hellow2.cpp -o hellow2 10 11$ ./hellow2.exe 12terminate called after throwing an instance of 'std::runtime_error'13 what(): locale::facet::_S_create_c_locale name not valid 14 15$
システムロケールへの設定はできない→wcoutは使えない
(https://stackoverflow.com/a/20181564)
(9) 調べたことまとめ
上記から、MINGW64で正しく日本語を扱うには、
- wcoutを使わず、coutを使う
- msvcrtかWindowsAPIか外部ライブラリなどを使って自分でエンコーディングを変換する
- リテラルをマルチバイトの固定エンコーディングにする(シフトJISなど)とワイド文字経由のコードページ変換が2度必要になり、手間がかかるため、ワイド文字列のリテラルか、u8プレフィックス付きを使うべき(ただしC++20でchar型での扱いができなくなる)
なのかと思う
ただマルチバイト(cout)にすると、
- 入出力時に変換が必要になり、わざわざマルチバイトにして出力した文字列をWin32側でまたワイド文字列に戻すなどの処理が入りそう
普段使いしてる人の正しい日本語の扱い方を知りたい
補足情報(FW/ツールのバージョンなど)
msys/msys2-runtime 3.3.6-5
0 コメント