ujimushi(@旧sradjp(15364))の日記

旧スラドの日記の引越先です

Windowsのコマンドラインプログラムをmsys2のUCRT環境でビルドする方法を模索した記録

きっかけのようなもの

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 Windowssetlocale(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のライブラリに

  • 俺はutf-8を使って結果出すぜ!setlocale関数
  • utf-8文字列を使いたいぜmanifest

という事が伝われば,後はUCRT側でよしなに入力をutf-8に変換してプログラム側に渡してくれたり,出力文字コードをターミナル合わせて変換して渡してくれるようだ。 (以上は個人の感想です)

基本的にutf-8だけ考えてコーディングすればよいので,Julia言語との連携もうまくいきそうだ。