パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
Code::BlocksのPATH改変

統合開発環境Code::BlocksのPATH改変を可能な限り正確に記述する。

必要に応じCode::Blocksソースコードを参照する。PATH改変が子プロセス起動にどのように影響するかを解析し、主にCode::Blocks起動初期に発生する可能性のある子プロセス起動時エラーについて言及し、エラーの回避方法を示す。

環境変数PATH

MSYS2の導入したPOSIXサブシステムにおいて、コマンドラインツールは依存するダイナミックリンクライブラリ(DLL)と同じPOSIXサブシステムのbinディレクトリ(例えば/mingw64/bin)に存在し、容易にDLLを見つける事ができる。しかしコマンドラインツールのあるものは別ディレクトリの実行ファイルを子プロセスとして起動し、その実行ファイルがbinディレクトリのDLLに依存する。例えばmingw64サブシステムのコンパイラmingw-w64はC++のコンパイルに/mingw64/lib/gcc/x86_64-mingw32/13.2.0/cc1plus.exeを起動し(GCC 13.2.0 Manual 3.20 Specifying Subprocesses and the Switches to Pass to Them)、cc1plus.exeは/mingw64/bin/libgmp-10.dll、/mingw64/bin/libwinpthread-1.dll、/mingw64/bin/zlib1.dllに依存する。cc1plus.exeはリンク前のコンパイルだけを担当する子プロセスで、すなわち狭義のコンパイラである。

覚え書き
実行ファイルのDLL依存はPOSIXサブシステム環境のlddコマンドで確認できる。ただしlddはmingw64サブシステムでは正しくレポートするがmingw32サブシステムでは誤る。その理由は調査していないが、本サイトは64ビットMSYS2を導入しlddはmsys2サブシステムに属する64ビットアプリケーションであるためと考えられる。

POSIXサブシステムを利用する開発においても、ソースコードからビルドされた実行ファイル(開発プログラム)はPOSIXサブシステムのbinディレクトリと異なる専用のディレクトリに出力されるのが普通であろう。この開発プログラムがランタイムライブラリを動的リンクするなら(-staticオプション無しでコンパイルされているなら)、やはりbinディレクトリのDLL(例えば/mingw64/bin/libstdc++-6.dll)に依存する。

ウィンドウズはアプリケーションの依存するDLLがアプリケーションディレクトリに見つからない場合、コマンドサーチパスを示す環境変数PATHに従って探索する。POSIXサブシステム環境なら起動時に正しいPATHをデフォルト設定するので問題ないが、ウィンドウズ環境では正しくPATHを加えないと前述のコマンドラインツールと開発プログラムが実行できない。特に本サイトは32ビット/64ビット開発をターゲットとするため、二つのPOSIXサブシステムを一つのウィンドウズ環境から利用する。幸いこれは困難でなく、ウィンドウズはDLL探索でアーキテクチャの異なるDLLをスキップするため両者のディレクトリを任意の順番でPATHに加えれば良い。むしろGit for WindowsのカスタムMSYS2のようなアーキテクチャを同一とするPOSIX互換システムとの共存に注意を払う必要がある。

Code::BlocksのPATH改変

Code::Blocksは通常のウィンドウズアプリケーションとして起動時にグローバルな(または親プロセスの)環境変数PATHを継承する。PATHはCode::Blocksにより以下のように改変される。Code::Blocksは様々なツールと開発プログラムを子プロセスとして起動するが、子プロセスは起動した瞬間のPATHを継承する。

名称 追加ディレクトリ タイミング リセット
環境変数カスタマイズ ユーザー任意 Code::Blocks起動、環境変数セット変更 可能
コンパイラディレクトリの追加 コンパイラディレクトリ プロジェクト選択/選択喪失、ビルド、開発プログラム実行 不可
ライブラリディレクトリの追加 ライブラリディレクトリ(ユーザー任意含む) 開発プログラム実行 自動

環境変数カスタマイズ

一般に環境変数は環境変数カスタマイズ(Code::Blocks Manual 1.11.3 Configuration environment variables)で任意に変更でき、環境変数PATHも例外でない。[Settings|Environment|Environment variables]の[...|Set/Create/Clone/Remove envvar - set]で環境変数セットを選択し、[...|Setup (toggle) environment variables]で環境変数PATHを編集する。環境変数セットはアプリケーショングローバルには[...|Set/Create/Clone/Remove envvar - set]で選択するが、プロジェクトプロパティ[Project|Properties|EnvVars options]でその選択をオーバーライドできる。

未だカスタマイズされていないが既に定義されている環境変数(例えばCode::Blocks起動前から定義されている環境変数)はカスタマイズされる直前の値が記憶され、自身を参照(再帰参照)する場合にこの記憶値を用いることができる。記憶は一度だけでCode::Blocksを再起動しない限りクリアされない。カスタマイズはCode::Blocksの起動時(ただしデフォルトコンパイラディレクトリ追加の後)、環境変数セットの選択変更時、および選択されている環境変数セットの編集時に実行される。環境変数セットの選択変更や編集でカスタマイズが削除または無効化された環境変数はカスタマイズ直前の状態にリセットされる。

Code::Blocksの変数参照書式は$VARIABLE、$(VARIABLE)、${VARIABLE}、%VARIABLE%であるが(Code::Blocks Manual 3.1 Syntax)、環境変数カスタマイズにおいて%VARIABLE%だけが特別に記憶値を参照する。ただし記憶値が存在しない場合は通常の参照と変わらない。例えば以下のようにカスタマイズする。

PATH=C:\MyOwnDirectory;%PATH%
PATH=C:\YourDirectory;%PATH%

結果は以下となる。

C:\YourDirectory;(記憶されたPATH)

一方、以下のようにカスタマイズする。

PATH=C:\MyOwnDirectory;%PATH%
PATH=C:\YourDirectory;$(PATH)

結果は以下となる。

C:\YourDirectory;C:\MyOwnDirectory;(記憶されたPATH)

コンパイラディレクトリの追加

Code::Blocksはビルド時と開発プログラム実行時にPATH先頭にコンパイラディレクトリを自動追加する。

コンパイラは[Settings|Compiler|Global compiler settings|Selected compler]で選択し[...|Toolchain executables|Program Files]でデバッガを除く各実行ファイルを定義する(デバッガは[Settings|Debugger]設定を参照する)。コンパイラディレクトリとはこれら実行ファイルを探索するパスであり、[...|Toolchain executables|Compiler's installation directory]をマスターパスとして以下で構成される。

  • マスターパス
  • マスターパス\bin
  • [...|Toolchain executables|Additional Paths]の追加絶対パス

コンパイラディレクトリ自動追加はこれらをPATH先頭に追加し、重複チェックを実施して後ろの重複を削除する。一度自動追加したコンパイラディレクトリは別のコンパイラディレクトリを自動追加してもPATH中に残り、これをリセットするには環境変数カスタマイズでPATHを再設定するかCode::Blocksを再起動するしかない。自動追加されるコンパイラディレクトリは以下のうちのいずれかである。

  • [Settings|Compiler|Global compiler settings|Selected compler]で[...|Set as default]とされたデフォルトコンパイラのコンパイラディレクトリ(デフォルトコンパイラディレクトリ)
  • [Project|Build options]のプロジェクトオプションについて[...|Selected compiler]で選択されたコンパイラのコンパイラディレクトリ(プロジェクトコンパイラディレクトリ)
  • [Project|Build options]のターゲットオプションについて[...|Selected compiler]で選択されたコンパイラのコンパイラディレクトリ(ターゲットコンパイラディレクトリ)

プロジェクト選択時はプロジェクトコンパイラディレクトリが自動追加される。ワークスペースを閉じるなどでプロジェクト選択を喪失する時はデフォルトコンパイラディレクトリが自動追加される。

ビルド時はそれがプロジェクトターゲットであればプロジェクトコンパイラディレクトリとターゲットコンパイラディレクトリがこの順番で自動追加される。先頭に追加されるためPATHではターゲットコンパイラディレクトリがプロジェクトコンパイラディレクトリの前となる。一方、プロジェクトを使用しない単一ファイルであればデフォルトコンパイラディレクトリが自動追加される。

開発プログラム実行時は、開発プログラムがプロジェクトターゲットからビルドされた場合は(ターゲットコンパイラディレクトリではなく)プロジェクトコンパイラディレクトリが自動追加される。開発プログラムがプロジェクトを使用しない単一ファイルからビルドされた場合はデフォルトコンパイラディレクトリが自動追加される。

以下に生成するPATHをまとめる。一つのコンパイラだけを使用する場合はデフォルトコンパイラ、プロジェクトコンパイラ、ターゲットコンパイラを同じとするのが通常で、かつディレクトリの重複はPATHから削除されるため、これらのPATH改変は問題とならない。しかし本サイトは32ビット/64ビット開発をターゲットとし二つのコンパイラを切り替えるため、これらは必ずしも同じにならない。

ビルド手段 タイミング 生成されるPATH
プロジェクトターゲット プロジェクト選択 プロジェクトコンパイラディレクトリ;$(PATH)
ビルド ターゲットコンパイラディレクトリ;プロジェクトコンパイラディレクトリ;$(PATH)
開発プログラム実行 プロジェクトコンパイラディレクトリ;$(PATH)
プロジェクトを使用しない単一ファイル プロジェクト選択喪失 デフォルトコンパイラディレクトリ;$(PATH)
ビルド デフォルトコンパイラディレクトリ;$(PATH)
開発プログラム実行 デフォルトコンパイラディレクトリ;$(PATH)

ライブラリディレクトリの追加

Code::Blocksは開発プログラム実行時にコンパイラディレクトリがPATHに自動追加された後、以下で構成されるライブラリディレクトリをさらに自動追加する。

  • ワーキングディレクトリ
  • [Project|Build options]のプロジェクトオプションについて[...|Serch directories|Linker]で設定されたディレクトリ(プロジェクトライブラリディレクトリ)
  • [Project|Build options]のターゲットオプションについて[...|Serch directories|Linker]で設定されたディレクトリ(ターゲットライブラリディレクトリ)

ライブラリディレクトリの先頭は常にワーキングディレクトリだが、残りの順番はターゲットオプションの[...|Search directories|Linker|Policy]が定めるプロジェクトオプションへの追加ポリシーに従う。選択されているコンパイラについて[Settings|Compiler|Global compiler settings|Selected compler]が[...|Search directories|Linker]で設定するディレクトリ(コンパイラライブラリディレクトリ)は、このライブラリディレクトリに含まない。自動追加されたライブラリディレクトリは開発プログラム起動後にリセットされ他の操作に影響しない。

PATHにライブラリディレクトリを加えるのは不自然だが以下を理由とする。Code::Blocksはマルチプラットフォーム対応でユニックスライク版も存在するが、ウィンドウズと異なりユニックスライクはダイナミックリンクライブラリ(ユニックスライクでは共有ライブラリと呼ぶ)をスタティックリンクライブラリと同じディレクトリに置くのを一般的とする。Code::Blocksはユニックスライクの考え方に従い、ライブラリディレクトリをダイナミックリンクライブラリ探索パスに加える。この探索パスはユニックスライクではLD_LIBRARY_PATHだが、ウィンドウズではコマンドサーチと共有のPATHである。

以下に生成されるPATHをまとめる。

追加ポリシー 生成PATH
Use project options only .;プロジェクトライブラリディレクトリ;$(PATH)
Use target options only .;ターゲットライブラリディレクトリ;$(PATH)
Prepend target options to project options .;ターゲットライブラリディレクトリ;プロジェクトライブラリディレクトリ;$(PATH)
Append target options to project options .;プロジェクトライブラリディレクトリ;ターゲットライブラリディレクトリ;$(PATH)

Code::Blocksの子プロセス

Code::Blocksは多くの子プロセスを起動する。本サイトはmingw32/mingw64サブシステムを利用し、子プロセスがどちらかのランタイムDLL依存となる場合がある。前述のようにコンパイラの子プロセスであるcc1plus.exeと-staticオプション無しでコンパイルされた開発プログラムは、PATHにランタイムDLLを収めるディレクトリ(例えばC:\msys64\mingw64\bin)を含まないと起動できない。

子プロセス起動時のPATH

PATHはCode::Blocks起動時から様々に追加されるので初期に起動できなかった子プロセスが後に問題なく起動できるようになる場合があり、一見再現性の無いCode::Blocks起動初期のエラーとして経験する。例えば以下の状況を考える。mingw32/mingw64コンパイラとはmingw32/mingw64サブシステムのコンパイラ、mingw32/mingw64サブシステムbinとはmingw32/mingw64サブシステムのbinディレクトリとする。

  • PATHがmingw64サブシステムbinを含まない環境からCode::Blocksを起動する。
  • デフォルトコンパイラがmingw32コンパイラである。
  • 起動時環境変数カスタマイズでmingw64サブシステムbinをPATHに加えない。
  • プロジェクトオプションコンパイラをmingw32コンパイラとし、ターゲットオプションコンパイラをmingw64コンパイラとした64ビットターゲットを持つプロジェクトを開く。64ビットターゲットは既に-staticオプション無しでビルドされていて、実行ファイルはmingw64サブシステムbinの外に置かれているものとする。

起動時はプロジェクトが選択されておらず、選択喪失したとしてデフォルトコンパイラディレクトリがPATH先頭に追加される。プロジェクトを開けばそれが選択されたとしてプロジェクトコンパイラディレクトリがPATH先頭に追加される。本例ではどちらもmingw32コンパイラで、PATHは以下となる。

C:\msys64\mingw32\bin;C:\msys64\mingw32;(Code::Blocks起動前のPATH)

このプロジェクトを開いただけでcc1plus.exeの起動失敗を示すアプリケーションエラーダイアログが表示される。ここで起動されたcc1plus.exeはmingw64サブシステムのランタイムDLLに依存しながらそのディレクトリの外に置かれていて、かつそのディレクトリにPATHが通っていないためである。プロジェクトを開いただけでcc1plus.exeが起動される理由は後に詳解する。

さらに64ビットターゲットを選択し[Build|Run]する。起動直前にプロジェクトコンパイラディレクトリ(mingw32コンパイラディレクトリ)がPATH先頭に追加されるが、重複は削除されPATHは結果的に変わらない。実行ファイルはcc1plus.exeと同じ理由で起動失敗しエラーダイアログが表示される。

覚え書き
ここでのエラーダイアログのメッセージ「アプリケーションを正しく起動しませんでした(0xc000007b)...」は、64ビット実行ファイルがmingw32サブシステムbinの32ビットDLLのロードを試みた結果を示す。仮にデフォルトコンパイラおよびプロジェクトコンパイラを*No Compiler*とすれば実行ファイルはDLLを発見できず、その場合は発見できなかったDLL毎にエラーダイアログ「*****.dllが見つからないため、コードの実行を続行できません...」を表示する。

64ビットターゲットを選択したまま[Build|Build]すればmingw64サブシステムbinを含むターゲットコンパイラディレクトリ(mingw64コンパイラディレクトリ)がPATH先頭に追加される。cc1plus.exeはランタイムDLLを発見しコンパイラは問題なく実行できるようになる。

C:\msys64\mingw64\bin;C:\msys64\mingw64;C:\msys64\mingw32\bin;C:\msys64\mingw32;(Code::Blocks起動前のPATH)

以降に[Build|Run]すれば64ビット実行ファイルは正常に起動する。ただし起動直前にプロジェクトコンパイラディレクトリ(mingw32コンパイラディレクトリ)が先頭に再追加されている。

C:\msys64\mingw32\bin;C:\msys64\mingw32;C:\msys64\mingw64\bin;C:\msys64\mingw64;(Code::Blocks起動前のPATH)

プロジェクトを開くときのcc1plus.exe実行

コンパイラ(狭義のコンパイラやリンカなどを子プロセスとして起動するmingw-w64)と狭義のコンパイラ(子プロセスとして起動されコンパイルのみを担当するcc1plus.exe)の定義を再確認し、後者は引き続きcc1plus.exeとして参照する。プロジェクトを開くだけでなぜコンパイラが実行されcc1plus.exeを起動するに至るかを説明する。

Code::Blocksはワークスペースが変更されると、それぞれのプロジェクトを選択してソースコードを解析し、エディタのコード補完を目的とするプロジェクト毎のデータベースを作成する。プロジェクトを開くこともワークスペースを変更することになり、開かれたプロジェクトのデータベースを作成する。データベース作成にあたり情報収集を目的とするオプション付きでコンパイラが実行され、cc1plus.exeが起動される。

  • プロジェクトオプションコンパイラを-dM(組み込みマクロを含み全てのマクロ#defineを列挙)および-E(プリプロセッシングで停止)オプションで実行し、出力からプロジェクトのコンパイラ定義済みマクロを取得する。
  • プロジェクトオプションコンパイラと全てのターゲットオプションコンパイラを-v(cc1plus.exe実行コマンドを表示)および-Eオプションで実行し、出力解析からそれぞれのcc1plus.exeインクルードディレクトリを取得する。これらの結果を全て結合して、プロジェクトのcc1plus.exeインクルードディレクトリとする。

-dMオプションから取得されるコンパイラ定義済みマクロは__DBL_MIN_EXP__や__FLT32X_MAX_EXP__など多数で、これらはエディタコード補完の対象となる。-vオプションから取得されるcc1plus.exeインクルードディレクトリは標準ライブラリなどの探索対象となるディレクトリであり、例えば以下である。

#include "..." search starts here:
#include <...> search starts here:
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/../../../../include/c++/8.2.1
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/../../../../include/c++/8.2.1/x86_64-w64-mingw32
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/../../../../include/c++/8.2.1/backward
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/include
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/../../../../include
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/include-fixed
C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.2.1/../../../../x86_64-w64-mingw32/include
End of search list.

プロジェクト選択時にプロジェクトコンパイラディレクトリはPATHに自動追加され、プロジェクトオプションコンパイラは問題なく実行できる。一方ターゲットコンパイラディレクトリはPATHに自動追加されないため、デフォルトおよびプロジェクトと異なるターゲットオプションコンパイラはCode::Blocks起動初期にcc1plus.exeの起動に失敗する。

覚え書き
コンパイラ定義済みマクロはプロジェクトオプションコンパイラから取得されるのに対し、cc1plus.exeインクルードディレクトリはプロジェクトオプションコンパイラと全ターゲットオプションコンパイラから取得される。取得対象とするコンパイラの範囲が両者で異なる理由は解らない。エディタコード補完の定義済みマクロがプロジェクトオプションコンパイラに限定されている事は容易に確認できる。例えばプロジェクトオプションコンパイラのみを*No Compiler*とすると定義済みマクロは全く補完されなくなる。
ワークスペースが変更されたりプロジェクトが選択されると、それぞれのイベントハンドラが選択プロジェクトのコード補完データベースを作成する。タイマーイベントは定期的にエディタをチェックし、必要なら表示ファイルのプロジェクトデータベースを作成する。エディタがフォーカスを獲得すれば、やはりタイマーイベントで表示ファイルのプロジェクトデータベースを作成する。このようにタイマーイベントを介する場合があるため操作から時間を置いてcc1plus.exeエラーが発生するという奇妙な経験をする。

子プロセス起動時エラーの回避

コンパイラ(実際はcc1plus.exe)あるいは開発プログラムを子プロセスとして起動する際に発生する可能性のあるエラーは、mingw32/mingw64サブシステムのbinディレクトリをあらかじめPATHに加える事で回避できる。以下にその方法をまとめるが、本サイトのセットアップでは環境変数カスタマイズをアプリケーショングローバルで行うことにする。

  • ウィンドウズ環境のグローバルPATH変数
  • 環境変数カスタマイズ
  • ライブラリディレクトリの追加

ウィンドウ環境のグローバルPATH改変

Code::Blocks起動前にウィンドウズ環境のグローバルなPATHの先頭にmingw32サブシステムとmingw64サブシステム両方のbinディレクトリを追加する。メリットとしてCode::Blocksの設定が不要である。デメリットとしては場合によりアーキテクチャの異なるDLLのロードを試み子プロセス起動がわずかに遅くなる。さらにより重要な問題として、Git for WindowsのカスタムMSYS2との共存がトラブルとなる可能性がある。

環境変数カスタマイズ

環境変数カスタマイズでPATH先頭にmingw32サブシステムとmingw64サブシステム両方のbinディレクトリを追加する。環境変数セットはアプリケーショングローバルに選択しておくか、またはプロジェクトプロパティで選択する。後者の方が他のコンパイラとの共存において有利と考えられるが、スクリプトに公開されておらずプロジェクト毎にウィザードで自動設定できない。いずれもデメリットとしては先と同じく、場合によりアーキテクチャの異なるDLLのロードを試み子プロセス起動がわずかに遅くなる。

ライブラリディレクトリの追加

ターゲットオプション毎にターゲットに合わせ、mingw32サブシステムあるいはmingw64サブシステムどちらかのbinディレクトリをライブラリディレクトリとして追加する。子プロセスとして起動される開発プログラムに適合するbinディレクトリだけを追加するため、メリットとしてアーキテクチャの異なるDLLのロードを試みない。デメリットとしてはコンパイラ(実際はcc1plus.exe)を子プロセスとして起動する際のエラーは解決できない。