Visual Studio 2022 で  UTF-8 のコンソールアプリのために CMake で参照できるライブラリを作成

Visual Studio 2022 を使用して C++ のプロジェクトを CMake で作成する方法を紹介しています。今回は、UTF-8 のプロジェクトを作る際の手続き（コンパイルオプション・マニフェスト・コードページの切り替え）を一つにまとめてライブラリ化し、CMake の find_package() から参照できるようにする方法を報告します。プロジェクトにマニフェストファイルを用意してターゲットに追加する手間が軽減されます。

# ライブラリの要件
https://qiita.com/kkoba775/items/3acfe243048266834722 でまとめたように、Visual Studio で UTF-8 を使ったプログラムを使う際には

* コンパイルオプションに `/utf-8` をつける。
* Active Code Page を UTF8 にするための manifest ファイルをプロジェクトに追加する。
* コンソールの出力文字コードを `SetConsoleOutputCP()` で変更する。

ことが有効です。これらを target_link_libraries() とヘッダファイルのインクルードだけで達成できるライブラリを作成します。

# 環境
* 開発環境として、Visual Studio 2022（Community）の「C++デスクトップ環境」がインストールされていること。
* CMake (最新版を推奨）がインストールされ、コマンドラインから cmake が実行できること。[^1]
* 環境変数の設定ができること。[^2]
* コマンドラインでの作業には「Developer Command Prompt for VS 2022」を使用すること。[^3]
[^1]: [https://cmake.org/](https://cmake.org/)  にある Latest Release の Windows x64 Installer を実行して 「Add CMake to the system PATH for all users」にチェックを入れてインストールする。
[^2]: 環境変数は、設定の「システム」＞「バージョン情報」＞「システムの詳細設定」から変更できます。```> set``` で環境変数の一覧が見れるので、設定が反映されていない場合には一旦 Windows からサインアウトしてサインインし直してください。
[^3]: スタートメニューの「Visual Studio 2022」フォルダ内にショートカットがあります。「ターミナル」の設定を変更し、「既定のプロファイル」に「Developer Command Prompt for VS 2022」を登録しておくと、エクスプローラーで作業するディレクトリを開き、コンテキストメニューから「ターミナルで開く」ことができて便利です。


# ディレクトリ構成
この記事では、ライブラリとそのソースは c:\dev にいれることにします。これは任意の場所に変更することができます。
```
dev/
├── utf8cp
    ├── include
    ├── lib
    ├── manifest
├── make
    ├── utf8cp
        ├── build
```
* ライブラリを作るための CMakeLists.txt は c:\dev\make\utf8cp に用意します。
* c:\dev\utf8cp にビルドしたライブラリがインストールされます。
* インストール後、 c:\dev\make\utf8cp\build は削除できます。

# ライブラリ utf8cp の作成
ディレクトリ ```C:\dev\make``` がなければ作成し、Developer Command Prompt for VS 2022 で以下を実行する。
```DOS
> cd \dev\make
> md utf8cp
> cd utf8cp
```
c:\dev\make\utf8cp に、utf8console.hpp, utf8api.manifest, CMakeLists.txt をそれぞれ次の内容で作成する。（Visual Studio Code をインストール済みなら ```> code .``` で作業を始めると良い。）
```utf8console.hpp
#pragma once
#include <windows.h>
#include <exception>

#ifndef __cpp_inline_variables
#error "C++17 is required for inline static varialbles." 
#endif

namespace automatic_utf8 {

struct chcp_utf8 {
    inline static UINT old_ = 0;
    chcp_utf8() noexcept {
        UINT cp = GetConsoleOutputCP();
        if (cp != 0 && cp != CP_UTF8) {
            old_ = cp;
            SetConsoleOutputCP(CP_UTF8);
            std::set_terminate(restore);
        }
    }

    ~chcp_utf8() noexcept {
        restore();
    }

    static void restore() noexcept {
        UINT cp = old_;
        if (cp != 0 && cp != CP_UTF8) {
            SetConsoleOutputCP(cp);
            old_ = 0;
        }
    }
};

struct automatic_chcp_utf8 {
    inline static chcp_utf8 chcp_;
};

}
```
* Visual Studio はデフォルト（`/Zc:__cplusplus` がない場合）では `__cplusplus` マクロによるバージョンチェックができないので、代わりに機能テストマクロを使った。 
```xml:utf8api.manifest
<?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>
``` 
```CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)

project(lib_utf8cp VERSION 0.1.0 
    DESCRIPTION "Active Code Page UTF-8" LANGUAGES CXX)

add_library(utf8cp INTERFACE)

target_compile_options(utf8cp INTERFACE "/utf-8")

target_sources(utf8cp INTERFACE 
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/utf8api.manifest>
    $<INSTALL_INTERFACE:manifest/utf8api.manifest> 
)

target_include_directories(utf8cp INTERFACE 
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>
    $<INSTALL_INTERFACE:include>
)

install(TARGETS utf8cp
    EXPORT utf8cp-config
)

install(EXPORT utf8cp-config
    NAMESPACE utf8cp::
    DESTINATION lib/cmake/utf8cp
)

install(FILES 
    ${CMAKE_SOURCE_DIR}/utf8console.hpp
    DESTINATION include
)

install(FILES
    ${CMAKE_SOURCE_DIR}/utf8api.manifest
    DESTINATION manifest
)
```
Developer Command Prompt for VS 2022 でライブラリのインストールを行う。
```DOS
> cd \dev\make\utf8cp
> md build
> cd build
> cmake -DCMAKE_INSTALL_PREFIX="c:\dev\utf8cp" ..
> cmake --build . --target INSTALL
```
# 環境変数の設定

次のうちいずれかを行う。上にある設定が優先される。

* 環境変数 ```utf8cp_DIR``` に ```c:\dev\utf8cp\lib\cmake\utf8cp``` もしくは `c:\dev` を設定する。
* 環境変数 `CMAKE_PREFIX_PATH` に ```c:\dev``` を設定する。
* 環境変数 `Path` に `c:\dev\bin` を追加する。

# 使い方
簡単な例で使い方を説明する。作業用の適当なディレクトリを用意して、以下の内容で ```main.cpp``` と ```CMakeLists.txt``` を作成する。```main.cpp``` は文字コードを UTF-8 にして保存する。
```main.cpp
#include <filesystem>
#include <iostream>
#include <fstream>
#include <string>

#include <utf8console.hpp>

int main() {
    std::cout << "数値以外の入力で例外が発生" << std::endl;
    std::string s;
    std::cin >> s;
    int a = std::stoi(s);

    std::filesystem::path fn = "結果";
    std::filesystem::create_directory(fn);
    fn = fn / "数値.txt";
    std::ofstream fo(fn);
    fo << "入力値 " << a << std::endl;
    std::cout << "正常終了" << std::endl;
    return 0;
} 
```
* 別に ```utf8console.cpp``` を用意して、そちらで ```#include <utf8console.hpp>``` としても良い。
* 同一ターゲットの複数のソースファイルで ```#include <utf8console.hpp>``` としても問題ない。
* コンパイルには C++17 以上の指定が必要。
```CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)

project(test_cp CXX)

find_package(utf8cp REQUIRED)

add_executable(main main.cpp)
target_link_libraries(main utf8cp::utf8cp)
```
Developer Command Prompt から
```
> md build
> cd build
> cmake ..
```
とすることで Visual Studio のプロジェクトである ```build\main.vcxproj``` とソリューションである ```build\test_cp.sln``` が作成される。いずれかを Visual Studio で開くことで IDE を用いた開発ができる。あるいは IDE を使わずに、コマンドラインから
```DOS
> cmake --build . --config Release
> Release\main.exe
数値以外の入力で例外が発生
775
正常終了
```
としてコンパイル・実行することもできる。実行を行ったディレクトリに、新しく"結果"ディレクトリが作られ、"数値.txt" に入力した数値が書き込まれる。数値以外を入力すると途中で停止するが、文字コードはもとに戻る。
```DOS
> chcp
現在のコード ページ: 932

> type 結果\数値.txt
蜈･蜉帛､ 775

> Release\main.exe
数値以外の入力で例外が発生
s

> chcp
現在のコード ページ: 932
```

# 参考にしたサイト

https://zenn.dev/tenka/articles/vc_utf8_prog2022_1

https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake

https://cpprefjp.github.io/