きっかけのようなもの
Julia言語の場合,外部コマンドとのやり取りを文字列で行うにはutf-8
を利用する必要があり,
Windows上でVisualStudioを使わないでc/c++言語で作成した自作コマンドを作ってやり取りする方法が見つかってなかった。
最近msys2の環境にUCRT64
が登場したので,その環境でお手軽に作成できないかとは思っていた。
昔見たMINGW64で作ったC++プログラムで正しく日本語を扱いたいのサンプル例によく似た次のようなプログラムを用意した。
#include <iostream> #include <string> int main(int argc, char* const argv[]) { if (argc > 1) { std::string str(argv[1]); std::cout << "[" << str << "]" << std::endl; } std::cout << "こんにちは" << std::endl; return 0; }
これをコンパイルして実行させる。作業を簡便に済ませるため,スタティックリンクしている。
$ g++ -Wall -static -o hello hello.cpp $ ./hello.exe "日本語" [日本語] 縺薙s縺ォ縺。縺ッ $ cmd Microsoft Windows [Version 10.0.19045.3693] (c) Microsoft Corporation. All rights reserved. >chcp 932 現在のコード ページ: 932 >.\hello.exe "日本語" [日本語] 縺薙s縺ォ縺。縺ッ >chcp 65001 Active code page: 65001 >.\hello "日本語" [{] こんにちは
ご覧の通り,プログラム内の文字列はutf-8
なので,出力はcodepageが65001の時しかうまくいかない。
逆にプログラム引数はcp932(シフトJIS)のようなので,cp932の時しかうまく表示されない。
なるべくソースリストの形を崩さず,よしなにうまくいく方法はないかを探っていく。
マニフェストファイル?
Windows版のEmacsの情報は常に探していて,「Windows 用 Emacs (Cygwin/MinGW) の UTF-8 対応改善実験」の記事を見つけた。
実験として次のようなマニフェストファイルを実行ファイルにマージしてutf-8
対応にしようとする試みだった。
utf-8.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyIdentity type="win32"/> <application> <windowsSettings> <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage> </windowsSettings> </application> </assembly>
UCRTならこれだけでutf-8
に対応するのか?と思い,manifest
ファイルをmsys2のgccで含められるのかネットでぐぐった。
すると,MinGWのgccでWindows のビジュアルスタイルに対応するの記事を見つけたので,
さっそく上記のmanifestファイルを含めた実行ファイルを作成してみる。
utf-8.rc
#include <winresrc.h> CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "utf-8.manifest"
UCRTのシェルからコンパイルして実行すると…
$ windres.exe --input-format=rc --output-format=coff -o utf-8.res utf-8.rc $ g++ -Wall -static -o hello hello.cpp utf-8.res $ ./hello.exe "日本語" -bash: ./hello.exe: Permission denied
どうもうまくいかないらしい。
setlocale
そこで,更にぐぐってみると
paths with non-ASCII chars on Windowsに
setlocale(LC_ALL, ".UTF8")
を呼ぶべしとの回答があって,この方法を試してみた。
#include <iostream> #include <string> #include <locale.h> // 追加 int main(int argc, char* const argv[]) { setlocale(LC_ALL, ".UTF8"); // 追加 if (argc > 1) { std::string str(argv[1]); std::cout << "[" << str << "]" << std::endl; } std::cout << "こんにちは" << std::endl; return 0; }
それぞれの実行結果は次の通り。
$ g++ -Wall -static -o hello hello.cpp $ ./hello.exe "日本語" [] こんにちは $ cmd Microsoft Windows [Version 10.0.19045.3693] (c) Microsoft Corporation. All rights reserved. > chcp 932 現在のコード ページ: 932 > .\hello.exe "日本語" [] こんにちは >chcp 65001 Active code page: 65001 >.\hello "日本語" [] こんにちは
出力は全て正常に表示された。setlocale(LC_ALL, ".UTF8")
をすると,
出力文字列を自動的に出力先ターミナルの文字コードに変換してくれるらしい。
ただし,プログラム引数は全て表示がNGとなった。
VisualStudioに付属のmtコマンドでのマニフェストのマージ
ここで,mt
コマンドでmanifestのマージを試してみる。VisualStudio Community Editionからコマンドプロンプトを立ち上げる
> mt -inputresource:hello.exe;#1 -out:hello.manifest > mt -manifest hello.manifest -manifest utf-8.manifest -out:merged.manifest > copy hello.exe hello-utf8.exe > mt -manifest merged.manifest -outputresource:hello-utf8.exe;#1 > .\hello-utf8.exe "日本語" このアプリケーションのサイド バイ サイド構成が正しくないため、アプリケーションを開始できませんでした。詳細については、アプリケーションのイベント ログを参照するか、コマンド ライン ツール sxstrace.exe を使用してください。
ありゃ。うまくいかない。
ここで,う~んとutf-8.manifest
とかmerged.manifest
とか見て 「<assemblyIndentity type="win32"/>
に違和感がある」と思って,merged.manifest
の該当部分を削除。
そして再度実行
> mt -manifest merged.manifest -outputresource:hello-utf8.exe;#1 > .\hello-utf8.exe "日本語" [日本語] こんにちは
おお。いけた。
> chcp 65001 > .\hello-utf8.exe "日本語" [日本語] こんにちは
おお。いけるいける。
一晩寝てからよくよく考えてみると,<assemblyIndentity type="win32"/>
はWinMain
で起動しているプログラム用かもと思った。
なら元のmanifestから該当する部分を消せばいいかも。
改善版のマニフェストファイル
ということで,
hello.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> <application> <windowsSettings> <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage> </windowsSettings> </application> </assembly>
と
hello.rc
#include <winresrc.h> CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "hello.manifest"
を用意。
最初のようにUCRTのシェルからコンパイルして実行すると…
$ windres.exe --input-format=rc --output-format=coff -o hello.res hello.rc $ g++ -Wall -static -o hello hello.cpp hello.res $ ./hello.exe "日本語" [日本語] こんにちは
おお。うまくいった。
所感
どうも,UCRTのライブラリに
という事が伝われば,後はUCRT側でよしなに入力をutf-8に変換してプログラム側に渡してくれたり,出力文字コードをターミナル合わせて変換して渡してくれるようだ。 (以上は個人の感想です)
基本的にutf-8
だけ考えてコーディングすればよいので,Julia言語との連携もうまくいきそうだ。