21
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

WindowsでもC++でUTF-8で読み書きしたい、他OSと同じビルドシステムを使いたい

Last updated at Posted at 2020-05-03

対象読者

  • macOSやLinuxの作法 (BOMなしUTF-8・char型もUTF-8・Makefileやそれを出力できるビルドシステム使用) でC++を書いている・書きたい人
  • Cygwin・MinGW・MSYS・WSLのGCCでなく、MSVCでC++プログラムをコンパイルしたい人
  • C++でクロスプラットフォーム開発したい人

Windows(MSVC)でもそれ以外のOSでもビルドできるようにする

**特段の理由がなければCMakeを使いましょう。**最近はVisual Studioの対応も良好です。

ソースコードでUTF-8を使う場合、以下の2行を加えましょう。

add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")

この/utf-8オプションは、コードのプリプロセッシング・コンパイルを行う際に、入力をUTF-8とみなすという意味です。ないと殆どの日本語環境でShift-JISとみなされます。また、.editorconfigというファイルをルートに作成し、適切に設定する文字コード・改行コード・インデントスタイル等をエディタによらず統一できます。例を以下に示します。

.editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
tab_width = 8

[*.{py,rs}]
indent_size = 4

[{Makefile,*.{go,mk}}]
indent_style = tab

[*.md]
trim_trailing_whitespace = false

[*.{manifest,vcxproj,sln}]
charset = utf-8-bom

CMakeLists.txtには、

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>")

の行も加えるとよいです。これは、C++11以降を利用した場合にマクロ変数__cplusplusの値を利用中のC++のバージョンに合わせるという意味合いです。つけないと、C++98相当の値となります。(一応MSVC独自のマクロ変数_MSVC_LANGで有効化しているC++のバージョンの判定は可能です、__cplusplusでの判定と併用しましょう)
さらに、VS2019のMSVCはデフォルトでC++14なので、次の行も足してC++17に対応させるとよいでしょう。

set(CMAKE_CXX_STANDARD 17)

以上を反映させたCMakeLists.txtのサンプルを以下に掲載します。projectadd_executable()内は適宜変更してください。また、cmake_minimum_requiredの値がやや厳しく、Ubuntu 18.04やVisual Studio 2017が弾かれてしまいますが、このバージョンからデフォルトでadd_executableに渡すソースファイルのパスとして相対パスが使えるというのが理由です。foobar.exe.manifestに関しては後述します。

cmake_minimum_required(VERSION 3.13)
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(foobar CXX)

add_executable(foobar foobar.cpp foobar.exe.manifest)

CMake以外の他の手段としてはqmake・Meson・Blazeあたりでしょうか。Qtを使うならqmakeもなくはないです。(一応CMakeでもQtのコンパイルは可能ですが、QtStudioからの新規作成ができなかったような気がする)

Windowsでもワイド文字を使わずにUTF-8を扱う

戦略は2つあります。

  1. Windowsに限り、UTF-8の入出力をUTF-16のワイド文字の間で変換する
  2. 通常文字の入出力をShift-JISからUTF-8にする

Windowsに限り、UTF-8の入出力をUTF-16のワイド文字の間で変換する

互換性を重視するならば、この方法がよいでしょう。Boost.Nowideという非常に便利なライブラリがあります。これは、Shift-JISのプログラム引数(argv)や、他OSと非互換のワイド文字版のコンソール・ファイルI/OをUTF-8版のもので隠してくれます。
次のようなコードで、UTF-8対応は完了します。

Boost.Nowideスタンドアロン版
#include <nowide/args.hpp>
#include <nowide/iostream.hpp>
//#include <nowide/fstream.hpp> // ファイルの入出力をする場合

int main(int argc, char **argv) {
  nowide::args _(argc, argv);  // インスタンス「_」が生存している間argvはUTF-8版のものを指す
  nowide::cout << "日本語だよ\n"; // nowide::coutの中で「std::wcout << L"日本語だよ\n"」相当の処理がされる
  for(int i = 0; i < argc; ++i) {
    nowide::cout << "引数" << i << ": " << argv[i] << '\n';
  }
  return 0; // main終了直前、「_」が破棄され、argvは元のShift-JIS版に戻る
}  

問題はライブラリのビルド・リンクですが、VcpkgというC++ライブラリ用のパッケージマネージャで一発インストール可能です。公式ページの手順でインストールしたら、次のコマンドを実行してインストールします。

.\vcpkg install nowide:x64-windows-static

その後、CMakeLists.txtに次の2行を足します。[foobar]を実行ファイル名にリネームすることをお忘れなく。2行の間にadd_executableが入るような形がよいかもしれません。

find_package(nowide CONFIG REQUIRED)
target_link_libraries([foobar] PRIVATE nowide::nowide)

ビルドは次のように行います。

mkdir build
cd build
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=[Vcpkgのルート]\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static
cmake --build .

通常文字の入出力をShift-JISからUTF-8にする

おそらくWindows10専用の手法だと思いますが、今やWindows7・8は空気になりつつあるので無視します。

引数を使う場合

setlocalestd::locale::globalなどに空文字のデフォルトロケールを渡します。

C兼用
#include <locale.h>
#include <stdio.h>

int main(int argc, char **argv) {
  setlocale(LC_ALL, "");
  puts("日本語だよ");
  for(int i = 0; i < argc; ++i) {
    printf("引数%d: %s\n", i, argv[i]);
  }
  return 0;
}
C++専用
#include <locale>
#include <iostream>

int main() {
  std::locale::global(std::locale(""));
  std::cout << "日本語だよ\n";
  for(int i = 0; i < argc; ++i) {
    std::cout << "引数" << i << ": " << argv[i] << '\n';
  }
  return 0;
}

そして、以下のマニフェストファイル

<?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>

を埋め込みます。CMakeを利用する場合、ソースファイルと一緒にadd_executableに渡すだけでOKです。
このページを見なくても、自作のコマンドラインツールでマニフェストファイルをファイルに出力することができます。急いで作ったのでソース汚い、コマンド名微妙と散々ですが
Rustのインストールが済んでいれば以下の2行で生成完了です。Cargoという配布手段がなかったらRustなんて使っていない

cargo install --git https://github.com/tats-u/forceu8exe.git
forceu8exe manifest .\foobar.exe.manifest

引数は無視する場合(非推奨)

以下のmain()直後の文を丸パクすればOKです。マニフェストは不要です。このプログラムをコンパイルすれば、chcp 932なWindows(MSVC)でも、Linuxでも、文字化けせず「日本語だよ」と表示されます。

C兼用
#include <locale.h>
#include <stdio.h>

int main(void) {
  setlocale(LC_ALL,
#if _WIN32 && !__MINGW32__ && !__CYGWIN__
    ".UTF-8"
#else
    ""
#endif
    );
  puts("日本語だよ");
  return 0;
}
C++
#include <locale>
#include <iostream>

int main() {
  std::locale::global(std::locale(
#if _WIN32 && !__MINGW32__ && !__CYGWIN__
    ".UTF-8"
#else
    ""
#endif
    ));
  std::cout << "日本語だよ\n";
  return 0;
}

WindowsとUnix系OSとのロケール・文字種の違い

Unix系では、setlocaleで文字コードを指定すると、ワイド文字(wchar_t)の文字列をどのエンコードに従ってマルチバイト文字列(char)に変換するかが変わります。setlocaleはしてもしなくても、またどのような値を指定しても、マルチバイト文字列はそのまま(ソースコードに書いたまま)出力されます。また、現在ではマルチバイト文字のエンコーディングはほぼUTF-8固定なので、マルチバイト文字でも問題が起こらないと言えます。
Windowsでは逆です。Windows(NT・2000以降)はワイド文字がメインかつ内部処理はワイド文字(UTF-16)固定で、マルチバイト文字APIは環境によってエンコーディングが変わります。Windows10限定の特殊な設定をしない限り、例えば日本語圏ではShift-JIS、英語圏ではLatin-1です。なので、マルチバイト文字をうかつに扱えません。UTF-8が使えるようになったのはWindows10になってからです。無理やり1つのエンコーディングに固定することもできますが、Windows8.1以前か、そのエンコーディングが表す言語以外か、どちらかを切り捨てざるを得ませんでした。MSもマルチバイト文字を推奨しています。
今までは、MSVCもGCCも両方使うC++erはこの2つの作法の板挟みになっていました。しかもWindowsとそれ以外ではワイド文字の長さも違います。(Windowsが2バイト、他は4バイト)絵文字などが混じったら目も当てられません。
大人しく環境によって2つの文字を使い分ける、Qtの文字クラスに頼る、あるいは日本語または絵文字(や𠮷など一部の漢字)の取り扱いを諦めるなど、あの手この手でごまかしてきました。

Windows10でのUTF-8の扱いの改善

Windows10では、マルチバイト文字のエンコーディングをUTF-8にできるようになりました。コマンドchcp 65001によって開いているコンソールの入出力をUTF-8にするのが実用的になったのが最初で、後にOSデフォルトのエンコーディングもShift-JISではなくUTF-8にできるようになりました。
残る問題は個別ソフトの対応です。SJISの環境でもUTF-8の環境でもUTF-8で出力された日本語を表示できるようになればいいのですが、その方法は長らくマイナーでした。

Microsoft Docs

21
19
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?