VSCode + MinGW-w64 via MSYS2 で WIN32API の Unicode ビルド

Pocket

VSCode + MinGW-w64 via MSYS2 で WIN32API の Unicode ビルド
本記事は C++ Advent Calendar 2023 の6日目の記事だ。

5日目は @uyamae 氏の C++ をclang で解析するときに情報をvcxproj から取得する方法 だった。


Windows 上でサクッと C++ ビルド環境を用意したい

さて、Win32API のコードをビルドしたいけど Visual Studio を入れるまでもない場合、 MinGW の GCC C++ コンパイラ (g++) と Visual Studio Code (VSCode) の組み合わせでも割と快適にビルドできる。

しかし、 VSCode のドキュメントの手順 そのままでは マルチバイト・Unicode周りでハマったので、非ASCII 圏で問題ないように設定する方法を紹介する。

先に解決方法だけ示しておくと、

  1. tasks.json の g++ ビルド時の引数を書き加えてやりつつ

                 "command": "C:\\msys64\\ucrt64\\bin\\g++.exe",
                 "args": [
                     "-fdiagnostics-color=always",
    +                "-DUNICODE",
    +                "-D_UNICODE",
    +                "-municode",
    +                "-std=c++23",
                     "-g",
                     "${file}",
                     "-o",
                     "${fileDirname}\\${fileBasenameNoExtension}.exe"
                 ],
  2. C/C++ extension for VS Code の設定から、 以下の IntelliSense 設定も変えればよい

    • 定義:

      _DEBUG
      UNICODE
      _UNICODE
    • C 標準: c23

    • C++ 標準: C++23

VSCode + MSYS2 上の MinGW-w64 の組み合わせでの設定

順を追って説明しよう。

まず、前述の VSCode のドキュメントの手順 で環境を構築していく。

具体的には以下の通り。

  1. Visual Studio Code をインストール

  2. vscode に C/C++ extension for VS Code 拡張をインストール

  3. MSYS2 の最新リリース から最新のインストーラー (msys2-x86_64-yyyymmdd.exe) をインストール

  4. MSYS2 MINGW64 の方を立ち上げて以下のコマンドを実行し、ツールチェインをインストール

    pacman -S --needed base-devel mingw-w64-ucrt-x86_64-toolchain
    • boost 使いたければ、 mingw-w64-ucrt-x86_64-boost あたりもインストールしておこう
  5. OS の環境変数 PATHC:\msys64\ucrt64\bin を追加

  6. VSCode を開きなおして適当なフォルダを開き以下のような hellowin32api.cpp を作成

    #include 
    #include 
    
    int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPTSTR pCmdLine, int showCmd)
    {
        MessageBox(NULL, _T("こんにちは、世界."), _T("メッセージ"), MB_OK);
    
        return 0;
    }
  7. エディタの右上にある Play Debug ボタンのドロップダウンから Run C/C++ File を選択し、検出されたコンパイラ一覧から C/C++: g++.exe build and debug active file を選択する。

実行した結果を見てみると…

んん~?
文字化けしちゃうね。

文字化けしないようにワイド文字列(Unicode)でビルド

IntelliSense 上では LPTSTRwchar_t* となっているのに、実際のビルド結果は char* となっている。
UNICODE が定義されていないので、そりゃそうか。
MBCS 扱いでビルドされているのに、ソースコードが UTF-8 になっているので化けているようだ。

今時「マルチバイト文字」即ち MBCS でのビルドはないので、「ワイド文字」即ち Unicode を使うべく、 windows.h 用の UNICODEtchar.h 用の _UNICODE をそれぞれ定義する。

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>

...

今度はビルドエラー

C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/../../../../lib/libmingw32.a(lib64_libmingw32_a-crtexewin.o): in function `main':
C:/M/B/src/mingw-w64/mingw-w64-crt/crt/crtexewin.c:67:(.text.startup+0xbd): undefined reference to `WinMain'
collect2.exe: error: ld returned 1 exit status

WinMain が定義されていないと出る。
_tWinMain マクロで定義される wWinMain がエントリーポイントになる想定だったが、ダメだったようだ。

MinGW-w64 の g++ の場合、エントリーポイントを unicode 対応させるには -municode のオプションの定義が必要らしい。

ビルド時の引数設定を書き換えるためには、 tasks.json を書き換えてやる。 ついでに UNICODE_UNICODE の定義や、 C++23 を使う設定も書き加えてやろう。(std::format とか使いたいじゃん?)

             "command": "C:\\msys64\\ucrt64\\bin\\g++.exe",
             "args": [
                 "-fdiagnostics-color=always",
+                "-DUNICODE",
+                "-D_UNICODE",
+                "-municode",
+                "-std=c++23",
                 "-g",
                 "${file}",
                 "-o",
                 "${fileDirname}\\${fileBasenameNoExtension}.exe"
             ],

IntelliSense とビルドの設定のズレも気になるので、 コマンドパレット (Ctrl+Shift+P) から C/C++: 構成の編集 (UI) を選択して開いた C/C++ 拡張の設定 から、以下のように設定しておく。

  • 定義:

    _DEBUG
    UNICODE
    _UNICODE
  • C 標準: c23

  • C++ 標準: gnu++23

ここまで設定して改めてビルドと実行してみると、

無事意図通り出力できた。

ちなみに C++ ではあまり一般的ではないが、先日の C# の場合と同様 .editorconfig を使ってブロック手前に改行コードが存在する整形方式(オールマンスタイル)から、改行コードが無い整形方式(K&Rスタイル)に変更できる。
C++ EditorConfg の書式に則って .editorconfig ファイルを作成し、以下のように記載しよう。

[*.{c++,cc,cpp,cxx,h,h++,hh,hpp,hxx,inl,ipp,tlh,tli}]

cpp_new_line_before_open_brace_namespace = same_line
cpp_new_line_before_open_brace_type = same_line
cpp_new_line_before_open_brace_function = same_line
cpp_new_line_before_open_brace_block = same_line
cpp_new_line_before_open_brace_lambda = same_line
cpp_new_line_scope_braces_on_separate_lines = false
cpp_new_line_before_catch = false
cpp_new_line_before_else = false
cpp_new_line_before_while_in_do_while = false

おまけ:強い心で MBCS を排除しよう

今時非 Unicode のビルドなんて一切気にする必要はないぜ、という強い心を持つなら、 tchar.h のインクルードも _UNICODE の定義もいらず、以下のように書いてしまってよい。

#define UNICODE
#include <windows.h>

int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPWSTR pCmdLine, int showCmd)
{
    MessageBox(NULL, L"こんにちは、世界.", L"メッセージ", MB_OK);

    return 0;
}

コンパイラオプションで定義しているので #define UNICODE は二重定義なのだけれど、「このソースは MBCS を排除している」という気持ちを示すために各ソースコードの先頭に書いて示しておくといいんじゃないかな。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください