C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

Thunderbird-68.3.1 で可能な本当のダークモードの設定方法のメモ: メッセージ本文領域にも闇を

f:id:USAGI-WRP:20191218144528p:plain

Dark Theme を使いたい人は↑こうしたいと思って Dark Theme を選択すると思います。たいてい。なので、標準搭載されている Dark Theme は「そうじゃない…」ってなると思います。私はなりました。標準搭載の Dark Theme やアドオンマネージャーでインストールできる自称 Dark 系のテーマでは、特にメッセージ本文の部分が白背景と黒文字のままで、電子メールアプリの本体部分みたいなところが眩しすぎてつらいままです。つらいです。

今回はこの ShadowBird で画面写真のように安らぎを得ました。この repos の README にある 3. (Optional) の部分がこの問題にとっては重要です。

プロファイルディレクトリー( Thunderbird の「ヘルプ」→「トラブルシューティング情報」の「プロファイルフォルダー」の「フォルダーを開く」 )に user.js を作成して、

user_pref("browser.display.foreground_color", "#b1b1b3");
user_pref("browser.display.background_color", "#38383d");

を書きましょうと案内されています。これで幸せになれます。あなたの目にも眩しすぎる光が心の平穏を乱さず適度に降り注ぐ未来が訪れますようにお祈り(このメモ)を残します。

参考

std::mutex vs. boost::mutex (1.71.0) vs. Windows CRITICAL_SECTION with VS2019 and Windows 10

現在の Windows 10 で Visual Studio 2019 の C++ プロジェクトので一般的に採用しやすい Mutex の各種の実装の実効速度的な優位性についてのメモです。特に結論とかべき論を展開する気はないのであくまでも参考程度に図ってみたらこういう結果になる事もあった、程度の事です。

条件

今回、CPUに本気を出させていませんが、このメモでは評価対象の実効速度性能を同じ条件で比較できればよいので気にしなくてよいです。

計測用に書いたコードはメモの末尾につけます。内容は、

  • std::pow による計算を Mutex ( std::mutex / boost::mutex / Windows CRITICAL_SECTION ) で排他制御しながら一定回数(=1,000,000)繰り返す負荷」を
  • 「同時に一定数(=8)のスレッドから処理させる計測」を
  • 「ばらつきも見るために一定回数(=8)繰り返す」

という実装にしました。

結果は、 簡単な開発環境のバナー --> 計測前の warm up --> std::mutex --> boost::mutex --> Windows CRITICAL_SECTION の順に出力しています。 calculated value は各計測バッチごとに、負荷による計算の結果(値そのものに意味はありません)を表示しています。 warm up は排他制御していないので計算結果は実行ごとに競合が発生し変化し得ます。計算負荷の中心は{ネイピア数}を{円周率の指数関数で増幅}/{1/円周率の指数関数で減衰}を繰り返す内容です。

Result ( release: /Ox )

[the Benchmark of the Windows Mutices]
_MSC_VER = 1923
_DEBUG = (undefined: RELEASE BUILD)
_WIN32_WINNT = 0x0A00
<warm up>
wall=0.19 user=1.28 system=0.00 total(u+s)=1.28 p(t/w)=680.9% | caclulated value = 2.71828
wall=0.18 user=1.31 system=0.00 total(u+s)=1.31 p(t/w)=718.4% | caclulated value = 2.71828
wall=0.21 user=1.63 system=0.00 total(u+s)=1.63 p(t/w)=786.5% | caclulated value = 2.71828
wall=0.21 user=1.53 system=0.00 total(u+s)=1.53 p(t/w)=741.1% | caclulated value = 2.71828
wall=0.21 user=1.53 system=0.00 total(u+s)=1.53 p(t/w)=743.2% | caclulated value = 23.1407
wall=0.20 user=1.56 system=0.00 total(u+s)=1.56 p(t/w)=788.9% | caclulated value = 2.71828
wall=0.21 user=1.52 system=0.00 total(u+s)=1.52 p(t/w)=734.6% | caclulated value = 2.71828
wall=0.21 user=1.53 system=0.00 total(u+s)=1.53 p(t/w)=742.8% | caclulated value = 2.71828
<std::mutex>
wall=1.00 user=7.48 system=0.00 total(u+s)=7.48 p(t/w)=749.6% | caclulated value = 2.71828
wall=0.89 user=6.92 system=0.00 total(u+s)=6.92 p(t/w)=773.8% | caclulated value = 2.71828
wall=1.07 user=8.17 system=0.00 total(u+s)=8.17 p(t/w)=765.8% | caclulated value = 2.71828
wall=1.01 user=7.70 system=0.00 total(u+s)=7.70 p(t/w)=759.4% | caclulated value = 2.71828
wall=0.94 user=7.11 system=0.00 total(u+s)=7.11 p(t/w)=758.8% | caclulated value = 2.71828
wall=0.90 user=7.06 system=0.00 total(u+s)=7.06 p(t/w)=783.4% | caclulated value = 2.71828
wall=0.93 user=7.19 system=0.00 total(u+s)=7.19 p(t/w)=773.7% | caclulated value = 2.71828
wall=0.93 user=7.16 system=0.00 total(u+s)=7.16 p(t/w)=770.9% | caclulated value = 2.71828
<boost::mutex>
wall=0.91 user=0.53 system=1.03 total(u+s)=1.56 p(t/w)=172.6% | caclulated value = 2.71828
wall=0.91 user=0.64 system=0.89 total(u+s)=1.53 p(t/w)=169.1% | caclulated value = 2.71828
wall=0.91 user=0.73 system=0.80 total(u+s)=1.53 p(t/w)=168.3% | caclulated value = 2.71828
wall=0.88 user=0.69 system=0.78 total(u+s)=1.47 p(t/w)=166.2% | caclulated value = 2.71828
wall=0.91 user=0.67 system=0.91 total(u+s)=1.58 p(t/w)=173.9% | caclulated value = 2.71828
wall=0.90 user=0.83 system=0.63 total(u+s)=1.45 p(t/w)=161.2% | caclulated value = 2.71828
wall=0.90 user=0.66 system=0.98 total(u+s)=1.64 p(t/w)=181.4% | caclulated value = 2.71828
wall=0.91 user=0.58 system=0.94 total(u+s)=1.52 p(t/w)=166.9% | caclulated value = 2.71828
<Windows CRITICAL_SECTION>
wall=1.77 user=6.55 system=3.03 total(u+s)=9.58 p(t/w)=539.8% | caclulated value = 2.71828
wall=1.74 user=5.89 system=3.16 total(u+s)=9.05 p(t/w)=519.4% | caclulated value = 2.71828
wall=1.75 user=5.00 system=3.06 total(u+s)=8.06 p(t/w)=459.9% | caclulated value = 2.71828
wall=1.75 user=6.33 system=3.03 total(u+s)=9.36 p(t/w)=535.1% | caclulated value = 2.71828
wall=1.75 user=6.61 system=2.89 total(u+s)=9.50 p(t/w)=543.9% | caclulated value = 2.71828
wall=1.75 user=5.73 system=3.41 total(u+s)=9.14 p(t/w)=521.9% | caclulated value = 2.71828
wall=1.77 user=5.80 system=3.67 total(u+s)=9.47 p(t/w)=536.4% | caclulated value = 2.71828
wall=1.75 user=6.47 system=3.20 total(u+s)=9.67 p(t/w)=552.8% | caclulated value = 2.71828

これを見た私はこの性能評価をしようと思ったきっかけに対して「ぁー」という感想を得られました。わたしは満足。実用上のヒントとしては十分。いまのところさらにINTERNALは想像以上の興味はないので。

Result ( Debug: /Od )

[the Benchmark of the Windows Mutices]
_MSC_VER = 1923
_DEBUG = (defined: DEBUG BUILD)
_WIN32_WINNT = 0x0A00
<warm up>
wall=0.58 user=4.63 system=0.00 total(u+s)=4.63 p(t/w)=790.6% | caclulated value = 23.1407
wall=0.59 user=4.70 system=0.02 total(u+s)=4.72 p(t/w)=798.1% | caclulated value = 23.1407
wall=0.60 user=4.81 system=0.00 total(u+s)=4.81 p(t/w)=796.2% | caclulated value = 23.1407
wall=0.58 user=4.59 system=0.00 total(u+s)=4.59 p(t/w)=797.4% | caclulated value = 2.71828
wall=0.58 user=4.63 system=0.00 total(u+s)=4.63 p(t/w)=791.5% | caclulated value = 2.71828
wall=0.59 user=4.59 system=0.00 total(u+s)=4.59 p(t/w)=784.9% | caclulated value = 2.71828
wall=0.60 user=4.69 system=0.00 total(u+s)=4.69 p(t/w)=787.7% | caclulated value = 2.71828
wall=0.59 user=4.69 system=0.00 total(u+s)=4.69 p(t/w)=790.2% | caclulated value = 23.1407
<std::mutex>
wall=4.69 user=37.03 system=0.00 total(u+s)=37.03 p(t/w)=788.8% | caclulated value = 2.71828
wall=4.67 user=36.80 system=0.03 total(u+s)=36.83 p(t/w)=788.9% | caclulated value = 2.71828
wall=4.62 user=36.58 system=0.02 total(u+s)=36.59 p(t/w)=791.5% | caclulated value = 2.71828
wall=4.63 user=36.58 system=0.00 total(u+s)=36.58 p(t/w)=789.9% | caclulated value = 2.71828
wall=4.67 user=36.92 system=0.00 total(u+s)=36.92 p(t/w)=790.4% | caclulated value = 2.71828
wall=4.68 user=37.22 system=0.00 total(u+s)=37.22 p(t/w)=794.9% | caclulated value = 2.71828
wall=4.63 user=36.55 system=0.00 total(u+s)=36.55 p(t/w)=789.3% | caclulated value = 2.71828
wall=4.67 user=37.08 system=0.00 total(u+s)=37.08 p(t/w)=793.3% | caclulated value = 2.71828
<boost::mutex>
wall=4.11 user=4.03 system=3.78 total(u+s)=7.81 p(t/w)=190.0% | caclulated value = 2.71828
wall=4.11 user=3.64 system=4.06 total(u+s)=7.70 p(t/w)=187.4% | caclulated value = 2.71828
wall=4.13 user=3.63 system=4.11 total(u+s)=7.73 p(t/w)=187.4% | caclulated value = 2.71828
wall=4.11 user=3.83 system=3.97 total(u+s)=7.80 p(t/w)=189.8% | caclulated value = 2.71828
wall=4.12 user=3.64 system=4.14 total(u+s)=7.78 p(t/w)=188.9% | caclulated value = 2.71828
wall=4.12 user=3.86 system=3.89 total(u+s)=7.75 p(t/w)=188.3% | caclulated value = 2.71828
wall=4.11 user=3.56 system=4.11 total(u+s)=7.67 p(t/w)=186.8% | caclulated value = 2.71828
wall=4.12 user=3.92 system=4.06 total(u+s)=7.98 p(t/w)=194.0% | caclulated value = 2.71828
<Windows CRITICAL_SECTION>
wall=3.62 user=22.45 system=2.83 total(u+s)=25.28 p(t/w)=698.5% | caclulated value = 2.71828
wall=3.60 user=22.27 system=2.91 total(u+s)=25.17 p(t/w)=698.8% | caclulated value = 2.71828
wall=3.63 user=22.47 system=2.81 total(u+s)=25.28 p(t/w)=696.7% | caclulated value = 2.71828
wall=3.65 user=23.31 system=2.77 total(u+s)=26.08 p(t/w)=714.3% | caclulated value = 2.71828
wall=3.63 user=23.52 system=2.20 total(u+s)=25.72 p(t/w)=708.8% | caclulated value = 2.71828
wall=3.64 user=23.55 system=2.63 total(u+s)=26.17 p(t/w)=719.1% | caclulated value = 2.71828
wall=3.62 user=22.28 system=2.92 total(u+s)=25.20 p(t/w)=695.6% | caclulated value = 2.71828
wall=3.61 user=21.94 system=3.14 total(u+s)=25.08 p(t/w)=694.8% | caclulated value = 2.71828

デバッグビルド版は今回のメモの目的ではないのでおまけ参考データ程度です。

この評価に使用したソースコード

Visual Studio 2019/C++: ファイル単位で設定された Configuration はどこにあるかのメモ

このメモを残した経緯

保守性の視点では使わない方がよいのですが、諸事情によりファイル単位で Configuration を施される事はしばしばあります。そして、プロジェクトの責任者が変わり、ドキュメントにも注意が残されず、ファイル単位で特殊な Configuration が行われている事が忘れ去られ、やがて誰かがプロジェクトを保守する際に不可解なエラーによりその存在に気が付く事になります。問題は、不可解なエラーの原因がどこにあるのか探り出すにはそれ相応の手間がかかる事です。

PCH の Use / Create の切り替え程度の一般性も高く、プロジェクトの構成やファイルツリーを見ただけでおおよそ想像がつくような場合は問題になりません。しかし、次のような場合は少々問題の対応に手間が必要です。

問題の例

  1. "昔々"、プロジェクトの黎明期に何らかの理由で Debug 用の Configuration でもいくつかの特定のソースファイルは /O1 で翻訳されるようファイル単位の設定を施しました(†1)
  2. それから何年もの時間が経過し、Visual Studio のバージョンアップやC++言語規格のバージョンアップも何回かあり、プロジェクトの近代化対応が求められるようになります
  3. "今"となっては謎多きプロジェクトの地下に広がる広大なダンジョンを安全に再探索するため、例えばプロジェクトの Congiguration に /RTCc を加えたとします:
1>cl : Command line error D8016 : '/O1' and '/RTCc' command-line options are incompatible
1>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets\Microsoft.CppCommon.targets(314,5): error MSB6006: "CL.exe" exited with code 2.

"今"の担当者はプロジェクトの Configuration を見直しますが、 C/C++ ⮕ Optimization を確認しても Disabled (/Od) が設定されています。ふぁっきゅー。

と、なるわけです。問題は Output を見ても一体どのファイルがわざわざ Debug ビルドで個別に /O1 を設定されているのかわからない事です。わっざへぇぅ。

  • (†1): 少なからずのプロジェクト黎明期の神話の時代の神々は諸事情によりプロジェクトチームへ小さな約束事を口伝で伝えてはくれますが、まともなドキュメントは残しません。

ファイル単位で Configuration が施されたファイルの探し方

  1. プロジェクトファイル .vcxprojテキストエディターで開きます (†1)
  2. /Project/ItemGroup (†2) を単位として "目grep" を開始します
    • おそらく先頭にある Label 属性を持った ItemGroup 要素はプロジェクト全体の Configuration です
    • <ItemGroup> はプロジェクトの設定のほか、ファイルの種類ごとにいくつか存在します
      • ソースファイル ( .cpp など) が対象の設定を探す場合は /Project/ItemGroup/ClCompile を含む <ItemGroup> を探します
      • 画像ファイル ( .ico など ) が対象の設定を探す場合は /Project/ItemGroup/Image を含む <ItemGroup を探します
      • リソースファイル ( .rc ) が対象の設定を探す場合は /Project/ItemGroup/Image を含む <ItemGroup を探します
    • たいてい後から施されたであろう設定はファイルの終端に近い方で発見できます
    • 探したい設定値やファイルの一部がわかっている場合はそれをヒントに <ItemGropu> から目的の Configuration を定義しているであろう部分を絞り込みます
      • 例えば /O1 を探す場合は MinSpace で探し、
      • /Ox を探す場合は MaxSpeed で探し…
      • つまり、通常ユーザーの見えるところに露出している設定値とは少し値の表現が異なるそれを探す必要があります

例えば↑の問題の例では、↓のような <ItemGroup><ClCompile> 子要素を持ち、その属性で対象となるファイルが指示され、さらにその子要素に目的の Configuration が埋もれています:

  <ItemGroup>
    <ClCompile Include="something.cpp">
      <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MinSpace</Optimization>
    </ClCompile>
  </ItemGroup>

探し方のコツさえわかれば、あとは目的に応じたファイル単位の Configuration を必要に応じて洗い出し、必要に応じた調整を施せます。もし、設定値の XML での値の表現の変化 ( /O1MinSpace のような ) がわからなければ、適当に小さな新規プロジェクトを作り、構成から x86 と Release を削除して .vcxproj を目grepしやすくした上で、残る唯一の Configuration へ目的の設定値を施した状態で .vcxproj をテキストエディターで開いて観察するとよいかもしれません。たぶん Microsoft もこの部分の詳細な仕様のドキュメントは公開していません。

ファイル1つだけ、構成も複雑ではなく該当の設定箇所を見つけやすい場合はこのように既存の設定をどうにかすることで問題を解決できます。しかし、あまりに構成が複雑怪奇な場合は諦めて完全にまっさらな Configuration あるいは .vcxproj を作り直してプロジェクトのソースを取り込むところから始めた方がかえって低コストになることもあるかもしれません。

  • (†1): ファイルフォーマットは XML に準拠しているのでテキストまたはXMLに特化した適当なエディターで開くとよいです
  • (†2): XPath です。

VS2019/C++: stdafx.h stdafx.cpp または pch.h pch.cpp つまり「プリコンパイル済みヘッダー」( Precompiled Headers ) の使い方のメモ

このメモの経緯

VSのPCH(PreCompiled Headers)機能は "使うためにユーザーがする事" はもうたぶん20年以上昔から変わっていないと思うので "いまさら" なのですが、ぼちぼち使い方がおかしくてビルド時間を無駄にしているプロジェクトに関わります。そこで、ごく簡単にPCHの使い方についてメモを残す事にしました。

PCHは本来、正しく使えている限りにおいては「たいていの一般的なプロジェクトで何度も開発中に繰り返されるビルド時間を大幅に圧縮する効果を見込める便利機能」です。但し、正しい使い方を知らない人がプロジェクトへ参加して、わけもわからないまま関連のファイルや設定を弄ってしまうと、ほんの少し正しくない使い方をしただけでも本来とは逆効果にビルド時間が増加するだけ&ライブラリーの管理に謎の面倒さが生じたりする厄介者扱いになり得てしまいます。

PCH の正しい使い方

Note: 特別な構成設定を作りこんでいない限りは、以下で Configuration の設定値を変更する前には、対象を Debug や Release などの個別の Configuration ではなく All Configurations に変更して、まとめて設定を行います。そうしない場合、構成ごとに同じ設定を何度も行う事になり単純作業の手間が増えます。(†1)

PCH を使いたいプロジェクトに対して:

  1. Configuration (構成) の C/C++ ⮕ Precompiled Headers ⮕
    1. Precompiled Header を Use (/Yu) に設定
    2. Precompiled Header File を pch.h に設定(†2)
    3. Precompiled Header Output File を $(IntDir)$(TargetName).pch に設定(†3)
  2. (1-b) で設定した pch.h ファイルを作成(中身は空のままでOK)
  3. (2) に対応した pch.cpp ファイルを作成(中身は空のままでOK)(†4)
  4. (3) で作成したソースファイルだけのプロパティーを開き Configuration を (1) と同様に辿り /Yu/Yc に変更(†5)
  5. プロジェクト単位の Configuration の Advanced で Forced Include File /FI[name]pch.h;%(ForcedIncludeFiles) を設定(†6)

これでプロジェクトのリビルドに成功すれば PCH の初期設定は無事に完了したと分かります。

無事に完了した場合は <string><vector> あるいは外部依存ライブラリーの重たいヘッダーファイルをプロジェクトのソースのあちこちで使っても、それらヘッダーファイル側の内容に変更が生じない限りはそれなりに高速にプロジェクトをビルドできるようになります。

PCH を設定した際の手順 (2) で作成した pch.h<string><vector> など、プロジェクトのどこかしらで #include している "内容の安定しているヘッダーファイル" の #include 定義を放り込みます。使用頻度は気にせず、プロジェクトで使う "内容の安定しているヘッダーファイル" は基本的に全て遠慮なく放り込んで構いません。

PCH の正しい使い方を維持する上で必要な注意点は実は1つだけです: 「内容が変化しやすい何かを加えないこと」

心配か、あるいは正しく設定できている気がするのに PCH が正しく動作していないと考えるような状況に陥ってしまっていれば、次項の「 PCH の誤った使い方」 を参考にして下さい。

  • (†1): 通常はプロジェクトで PCH を使う場合、特定の構成に設定する必要はありません。もちろん、 PCH はビルドの構成ごとに個別に動作する必要がありますが、 Visual Studio のプロジェクトではそうしたビルド中の中間ファイルや出力が混ざらないように $(Platform)$(Configuration) を変数としつつ構成を跨いで共通の設定が施された状態でプロジェクトが作成されます。もし、 Configuration ⮕ General の Output Directory や Intermediate Directory でそうした変数を用いないように必要に迫られて変更を施してある場合は、構成事に PCH の関連ファイルが混ざらないように個別に設定するか、改めて $(Platform)$(Configuration) を採用するか、何れかの対応が必要です。通常は $(Platform)$(Configuration) を素直に使い、 Configuration ごとの設定値の差異はできるだけ最小限とする事を保守コスト都合から著者はおすすめします。

  • (†2): pch.h は設定の「例」です。

    • お好みの名前を設定しても大丈夫です。
    • 古い Visual StudioMFC プロジェクト等では標準で stdafx.h 、 VS2019 のデスクトップアプリのプロジェクト等では pch.h がプロジェクトの新規作成時に設定済みになっています(たぶん)。
  • (†3): $(IntDir)$(TargetName).pch は設定の「例」で Visual Studio で作成したプロジェクトに初期設定される値と同じです。

    • お好みのパスを設定しても大丈夫です。
    • このファイルはビルド全体から見れば「中間ファイル」の1つなので、特別な理由が無ければ $(IntDir) の中へ出力します。通常 $(IntDir)$(Platform)\$(Configuration)\
  • (†4): MSDN を含め、おそらく巷の書店の入門書、あるいはこのブログのような技術的なメモのような多くの情報断片、そうしたもので PCH について解説を読むと、このファイルの中身は空ではなく1行だけ #include "pch.h" を記述するよう記されている事が多いと思います。私がこのメモで残す手順と手法ではそれは不要で、他の情報源でも慎重に内容を読めば同様にそれが最終的には多くの場合には不要だった事にも気づけると思います。他の情報源が誤っているわけではありませんし、おそらくそれらは PCH ならまずは PCH についての仕組みついて基礎を忠実に紹介する事を主眼としているだけです。このメモでは実用性を主眼としているため、そのような解説都合上の手順はすっ飛ばしています。

  • (†5): Visual Studio の Configuration は、ほとんどの場合にはソリューションエクスプローラーからプロジェクトを右クリックしてコンテキストメニューからプロパティーを開き、 "プロジェクト単位" で設定を調整します。しかし、ここで必要となるように、実はプロジェクトに含まれる "ファイル単位" でも Configuration を調整できます。

  • (†6): この設定の対象には、もちろん (3) で作成する pch.cpp も含まれます。この /FI オプションは PCH とは別の機能ですが、 PCH に併せて使うと非常に合理的で、結果的に PCH の設定時だけでなく、それ以降もプロジェクトで PCH を使う決断をした事で生じ得る保守コストの増加をおおむね 0 となる程度まで防げます。この設定を利用できない場合は PCH を使うプロジェクト内のすべてのソースコードファイルの先頭に #include "pch.h" を書き加え保守し続ける必要が生じます。その際は pch.cpp にも同様に #include "pch.h" を書く必要があります。

PCH の誤った使い方

このメモのここから先は、

  • 「気づける開発者がプロジェクトに参加していて、そうした中堅以上の技術者がコードレビューに参加できており、開発は Git 等で管理され変更ごとに branch からの pull-request によるマージプロセスを…」

そのような "まとも" な開発状態が理想的に継続できている場合は「そんなうっかりしないよ」的な内容ではあります。

"現実はなんとかよりかんとか"、と世間ではよく言われるようです。しばしば私もそれに同意します。

Bad case 1: pch.h ( stdafx.h ) を「プロジェクト内で共通するヘッダーファイルをまとめるところ」と勘違いしてる

違います:

  • ×「プロジェクト内で共通するヘッダーファイルをまとめるところ」
  • ◎「プロジェクト内で使用する 内容の安定したヘッダーファイル をまとめるところ」

です。

このケースが該当しそうな具体的な例としては、

  • ヘッダーファイルの内容の更新頻度の高い外部依存ライブラリーを pch.h#include している
  • 「プロジェクト内の複数個所で使うから」という理由でプロジェクト内やソリューション内で開発中の機能のヘッダーファイルを pch.h#include している

のようなことが想定されます。

プロジェクトごとに取り扱うヘッダーファイルの "更新頻度" の捉え方は異なりますが、著者の感覚としては…

  • × 数時間、数日、あるいは数週間程度ごとにヘッダーファイルの内容が機能追加や仕様変更によって変化するヘッダーファイル
    • 開発初期の黎明期にある個人が開発を管理するライブラリーの多く
    • プロジェクト内で開発中、あるいはしばしば調整の行われているヘッダーファイルなど
  • △ 数週間ごとにしばしばヘッダーファイルの内容が機能追加や仕様変更によって変化するヘッダーファイル
    • 開発が軌道にのり継続されていて、機能追加 Issue への対応が頻繁な、開発規模が比較的小規模なライブラリーの多く
  • 〇 数か月ごとにまとまった更新が発生する一般的に多くの安定した計画性をもって開発が行われているライブラリーのヘッダーファイル
    • Boost や GLFW や Eigen など多くの比較的規模の大きな OSS ライブラリーなど
    • Microsoft PPL や Qt などのIDEと事実上セットで提供されているような開発環境ごとに準標準的な大きなライブラリーなど
    • Windows SDK<windows.h> など
  • ◎ せいぜい数か月か数年ごとにバグ修正や脆弱性修正が行われる程度になった実質的に開発か終了して仕様が変化しない良い意味で枯れたライブラリーのヘッダーファイル
    • xerces や curlpp など
  • ◎ 標準ライブラリーのヘッダーファイル
    • <string><cstdio> など

×/△は pch.h へ入れてはダメという事ではなく、入れるのが誤った判断となる可能性が高い、という事です。例えば、プロジェクトに必要十分な機能が既に揃っていて、プロジェクト内からは更新する必要がないのなら、入れる選択肢もあります。

〇/◎は pch.h へ入れなければならない、という事ではなく、入れるのが正しい判断となる可能性が高い、という事です。例えば、長い間仕様が安定していた OpenSSL も脆弱性の問題から破壊的変更を伴う仕様変更を受け入れる必要が生じたり、修正頻度が × ほど上がる時期もありました。そういう事があれば、プロジェクトの PCH の対象からはさっくりと外して様子を見る事にしつつ、依存ライブラリーの脆弱性やバグ修正の対応にプロジェクトも素早く追従できるよう調整できるのが理想的には望ましいと著者は思います。

ImageMagick は master ブランチの更新頻度を見れば ×/△ ですが、多くの場合常に最新の master をプロジェクトへ取り込む必要はなく、事実上は安定していると判断できます。また、 Boost や OpenCV のように開発はアクティブでもメジャーバージョンやマイナーバージョンの区切りごとに扱いやすい提供形態がある場合に、プロジェクトが安定したバージョン系列で十分ならば PCH へ含めるのに適していますし、そうではなくプロジェクトから Beta や Alpha の開発ブランチを使いたいのであれば、もちろん PCH へ含めるのは誤りとなります。

Bad case 2: ビルドごとに内容が変化する"定数"を含んだヘッダーファイルを取り込んでいる

先ず、わかりやすい例から。

例えば、何らかの定数をヘッダーファイルが持つ場合:

  • ◎ 「真空中の光速」や「プランク定数」あるいは「π」のような硬い不変性を持つ定数
    • ⮕ 事実上問題になりません、 PCH へ入れましょう
  • 〇 開発中のアプリ独自の許容誤差範囲の定義 1.0e-686400 * 7 (秒単位の1週間) のような安定した定数
    • ⮕ たいていビルド頻度に対してそれらを見直す必要の生じる頻度はとても少ないのでたぶん大丈夫です、 PCH へ入れましょう
      • 但し、その値は安定しているにしても、開発に伴いヘッダーファイルへそのような定数の追加がしばしば行われるヘッダーファイルは×です
  • △ 諸事情により算出された調整用の定数値を含んでいる
    • ⮕ しばしば"再調整"が生じて変更されるようなものはダメです、 PCH からは除外して下さい
  • × プログラム的には定数ではあるが、ビルドごとに変化するバージョン番号や日付と時刻を定数として取り込んでいる
    • ⮕ ダメです、 PCH からは除外して下さい

もう少し分かりにくい例へ。

  • △ 構成単位で切り替えられる /D / #define で与えられるフラグや定数値を含んでいる
  • × 開発中にしばしば調整される /D / #define で与えられるフラグや定数値を含んでいる

しばしば比較的小さくない規模のプロジェクトや歴史の長いプロジェクトでは複数の /D / #define によりビルドを分岐している事があります。そのようなプロジェクトで PCH を導入する場合は事前に日常的な開発で取り扱う可能性のある /D / #define の組み合わせ毎に Configuration を整理しておき、かつそれらの Configuration で PCH が混ざらないように $(IntDir), $(Platform), $(Configuration) 等を扱うか、あるいは PCH を構成毎に個別のファイルとして生成するように設定しなければ PCH について誤った使い方となります。

このメモの著者が遭遇しやすい例では、開発中に用いる Configuration Debug の中で開発中の便利などにより切り替えるフラグ値を #define で管理していたり、その歴史的経緯を含んだまま PCH の運用を前提とせず何らかの都合で $(IntDir) を構成ごとに分けずに共有化してしまっていたり、そのような事がしばしばあります。結果、そうしたプロジェクトで PCH を採用するか、あるいは既にしていたとしても、正しい使い方にならない、あるいはなる時もあればならない時もありビルド時間が時折非常に長く感じられる事がある…、そのような事態に陥っている事があります。

これらは直接ヘッダーファイルがそのようなコードを含む場合にはたいてい未然に PCH への追加を回避されますが、ヘッダーファイルのインクルードツリーから間接的に含んでしまっている場合があります。(†1)

  • (†1): ヘッダーファイルのインクルードツリーは Visual Studio では Configuration ⮕ C/C++ ⮕ Advanced ⮕ Show Includes を Yes( /showincludes ) に設定した構成でビルドすると Output へ出力され、確認できます。

おまけ解説

  • 名称 $(IntDir)
    • たぶん "Intermediates-Directory" 的な発想が元になっていて、「中間生成物用置き場」の意味です。
    • 近年では採用される事の多くなった $(TargetName) のようないわゆる "長いが読んだら元のフレーズが明確にわかりやすい命名" になっていない理由は、おそらくこの変数が Visual Studio に登場した時代にはまだ一般的だった"変数名やファイル名は詰めて短縮/あるいはルールを決めて記号化して短くした方が計算機の都合には優しい"的な考え(その根拠はその時代よりもさらに何十年も昔のメモリーもCPUパワーもリソースがごく限られた時代に必要だった事情によります)
      • 整数型の int に由来する要素はありません。ぱっと見的に初心者は誤解しかねないので老婆心で追記しておきます。

参考

C++/Windows: WinMain/wWinMain な Windows デスクトップ向けアプリで std::cout / std::wcout とかをコンソールに直接出したいときのメモ

Visual StudioWindows デスクトップアプリを作る場合は WinMain / wWinMain がエントリーポイントのプロジェクトを扱うのが一般的です。コンソールアプリとして作って GLFW から画面を出したり imguiGUIを作ったり、コンソールアプリの実行時に Win32 API を叩いてウィンドウハンドルを操作して…というのは特別理由が無ければふつーはしません。

そうすると、 std::cout / std::wcout はじめ標準出力、標準エラー出力、標準入力を扱う仕組みが "コンソールへ文字列を出したい" という点では期待動作はしなくなります。でも、出したい時は:

// (A1) OutputDebugStringA / OutputDebugStringW
// ; 少し趣旨と違うけれどこれでいいなら、いいんじゃない的な代替手段。使うのかんたん
// #include <debugapi.h> // <-- windows.h に含まれるのでふつーは手いんくるーど要りません
OutputDebugStringW( _T( "ヘルシオ・ホットクック美味しいです♡" ) );
// (A2) std::cout が動く。でも使うまでに少しの手間が必要です
#include <iostream>
#include <fcntl.h> // _O_TEXT
#include <io.h> // _open_osfhandle

void somewhere_in_your_code()
{
    // プロセスにコンソールを割り当てる(ref:A2-1)
    AllocConsole();
    
    // コンソールで使う文字コードをUTF8へ切り替える
    //   切り替えないと日本では ANSI -> CP932 のままになります
    SetConsoleOutputCP(CP_UTF8);

    // 現在の std::cout, std::wcout が接続されたストリーム(=FILE*) stream_std_out を開く
    auto stream_std_out = []()
    {
      // internal-step-1:
      // 標準デバイス(=STD_OUTPUT_HANDLE=標準出力を示すフラグ)の"Windowsにおけるハンドル"(=wh_std_out)を取得
      auto wh_std_out = GetStdHandle(STD_OUTPUT_HANDLE); // (ref:A2-2)

      // internal-step-2:
      // 既存の標準出力のWindowsにおけるハンドル(=wh_std_out)を
      // CRT(C run-time)のファイル・ディスクリプター(=fd_std_out)として開く
      //   note: このファイル・ディスクリプターをRAII的に閉じる必要はありません (ref:A2-2-a)
      auto fd_std_out = _open_osfhandle((intptr_t)wh_std_out, _O_TEXT); // (ref:A2-3)

      // internal-step-3: 
      // 直前に開かれたCRTのファイル・ディスクリプターをCRTのストリーム(FILE*)として開く
      return _fdopen(fd_std_out, "w"); // (ref:A2-4)
    }();

    // 現在の std::cerr, std::wcerr が接続された stream を開く
    auto stream_std_error = []()
    {
      // ↑の cout/wcout と同様
      auto wh_std_error = GetStdHandle(STD_ERROR_HANDLE);
      auto fd_std_error = _open_osfhandle((intptr_t)wh_std_error, _O_TEXT);
      return _fdopen(fd_std_error, "w");
    }();

    // 現在の std::cin, std::wcin が接続された stream を開く
    auto stream_std_in = []()
    {
      // ↑↑の cout/wcout と同様
      auto wh_std_in = GetStdHandle(STD_INPUT_HANDLE);
      auto fd_std_in = _open_osfhandle((intptr_t)wh_std_in, _O_TEXT);
      return _fdopen(fd_std_in, "r");
    }();

    // C++ の標準ストリーム系(iostream系なやつら)を C の標準ストリーム系(cstdio系的なやつら)と同期する
    std::ios::sync_with_stdio( true ); // (ref:A2-5)


    // 現在のCRTストリームに接続されたCRTハンドルを閉じつつ、新たにCRTストリームへCRTハンドルを接続する
    freopen_s(&stream_std_out  , "CONOUT$", "w", stdout); // (ref:A2-6)
    freopen_s(&stream_std_error, "CONOUT$", "w", stderr);
    freopen_s(&stream_std_in   , "CONIN$" , "r", stdin );

#if 0
    // VS2019で著者が試した限りでは不要ですが、VS2005からしばらくでは必要かもしれません。
    // C++ の標準ストリームオブジェクトのエラー状態をクリアーする
    //   有効な標準ストリームが設定される前に扱おうとするとエラーフラグが立つ対策。
    //   VS2005以降ではコンソールへの読み書きの有無によらず常に実行時にエラーフラグが立つので必要「かも」しれません
    //   (著者は古いVSでわざわざ試していないので確認していませんが参考 ref:A2 によると必要な事が起こるかもしれないので一応メモへ残します)
    std::wcout.clear(); std::cout.clear(); // (ref:A2-7)
    std::wcerr.clear(); std::cerr.clear();
    std::wcin.clear();  std::cin.clear();
    std::wclog.clear(); std::clog.clear();
#endif

    // そうして、ようやくですが std::clog など使えるようになります
    std::cout << u8"UTF-8で🍵どーぞ" << std::endl;
    std::cout << u8"⛄🎄📦🎍🌸" << std::endl;
}

f:id:USAGI-WRP:20191207033750p:plain

…悲しい…。(通常の目的は達成できていますが…)

    std::cout << u8"UTF-8で🍵どーぞ" << std::endl;
    std::cout << u8"⛄🎄📦🎍🌸" << std::endl;

↑モダン・ウェブ・ブラウザーは優秀です…(たぶん、すべての絵文字が見えているでしょう…)。

f:id:USAGI-WRP:20191207034806p:plain

↑WSLの端末エミュレーターcmdより優秀です…。

f:id:USAGI-WRP:20191207035842p:plain

Windows Terminal (Preview)-0.7.3382.0 (A-2-8) は惜しい感じです。入力(IMEで「おちゃ」と入力して変換、確定)はハテナ・ダイヤモンドですが、echoからの標準出力の表示では期待動作して絵文字を表示できました。…これがOSのコンソールの標準に置き換わってくれると、内部的には cmd であれ powershell であれでもUNICODEハッピーな絵文字も使えて人生がだいぶ楽しくなると思うので20年以内にそうなってほしいなーくらいにゆるっと期待したいと思います。(現時点では少なくとも「Windowns Terminalは既存のコンソールを置き換えるものではない」的な事をMicrosoftは言ってたような気はします)

若干、はなしがそれましたが、目的は達成できています。

注意点として、 A-2 の方法で出したコンソールのウィンドウには画面右上におなじみのバツ印ボタンがついています。これを押すと、もともとのデスクトップアプリのプロセスも終了します。

APIを駆使してこのボタンを消したり、あるいはそもそもcmdではない端末エミュレーターへ標準出力をパイプしたり、そういう変態的な事もいちおう WindowsAPI を駆使すればたぶんできますが、素直にログウィンドウを作るなりライブラリーでどうにかするなり、ログをファイルかメモリーかデータベースか何れにせよどこかへ出すなりバッファリングするなりしておいて別プロセスで読み出せるようにするなり…。そもそもどうして std::cout とかを出したかったのか、その要求を考え直さないとただの変態的でテクニカルなソフトウェア・モダン・アートのごみができると思います…。

…でも🍣とかログに出したいですよね…2019年にもなって…。ぅぅ…。

おまけ: ↑の状況とはある意味では逆転して、コンソールアプリだけどエラーメッセージのダイアログを出したい、場合には?

// (O1) でんっ!って出るやつです
#include <crtdbg.h> // おまけのおまけ: MSVC++ では <string> ( --> <xstring> --> <iosfwd> --> <yvals.h> --> <crtdbg.h> ) とインクルードされたりもします
_RPTFW0( _CRT_ERROR, _T( "今夜はパッチェリ・アラビアータにしよう。Hは発音しなでくださいとか日本語に求めないでね!" ) );

_RPT マクロ "群" なので、たくさんのマクロのセットです。 W の有無は Widechar/ascii のそれ、F の有無はファイル名と行番号を自動挿入する/しない、末尾の数字はオプションの引数の数、の組み合わせです。第1引数でエラーの他にワーニングも出せます。注意として、この関数はデバッグ版のランタイム・ライブラリーにしか含まれないので、カオスでアーティスティックでテクニカルな事をしない限りは通常デバッグビルド構成でしか使用できません。

f:id:USAGI-WRP:20191207005603p:plain

参考

Visual Studio 2019 C++: Configuration に「絵文字」を使える、というメモ

f:id:USAGI-WRP:20191207072224p:plain

この画面写真↑には3つの構成(Configuration)が含まれています:

  1. Debug
  2. Release
  3. 🍣 tmp exp hoge 🍵

絵文字/everywhere です。上手く使うと構成が多数あったり、似た構成がある場合の間違い防止効果を期待した使い方もできそうです。

f:id:USAGI-WRP:20191207073353p:plain

全ての表示がUNICODE絵文字に対応できているわけではないようです。とはいえ、開発中に最も多く使う冒頭の画面写真の位置にある構成切り替えの表示が対応してくれていれば用途として、絵文字を使用する目的は達成できるので、この辺りはあまり気にしなくてもよいかもしれません。

おまけ追記

VS2017 でも同様にリネーム、表示できました。どこまで昔のバージョンまで対応できるか私はこれ以上確認できませんがあしからず。

Visual Studio 2019 C++: _DEBUG _WINDOWS _WIN32 _WIN64 _UNICODE NDEBUG WIN32 WIN64 UNICODE のメモ

シンボル 自動的に定義される?(predefined macro?) VSが新規作成で構成に追加? そもそも何? 効果ある?(*2)
_DEBUG yes ( msbuild ) yes ( Debug ) デバッグビルドを意味する識別用にmsbuild がプロジェクトの Configuration -> Advanced -> Use Debug Libraries が Yes で /LDd /MDd /MTd が有効になる場合に自動的に定義される。但し、プロジェクト作成時にConfiguration(Debug) -> C/C++ -> Preprocessor -> Preprocessor にも明示的に追記される。 yes
_WINDOWS no (*3) yes Windows向けを意味する識別用の定義。プロジェクト作成時にConfiguration -> C/C++ -> Preprocessor -> Preprocessor に明示的に追記される。(*3) no
_WIN32 yes ( cl ) no Windows の 32-Bit 版または 64-Bit 版で動く事を意味する識別用に cl によって自動的に定義される。 yes
_WIN64 yes ( cl ) no Windows の 64-Bit 版で動く事を意味する識別用に cl によって自動的に定義される。 yes
_UNICODE yes ( msbuild + ps ) (*1) yes UNICODEの使用を意味する識別用にプロジェクト作成時に自動的にプロパティーシートとして設定が追加され、 Configuration -> C/C++ -> Preprocessor -> Preprocessor に追記される %(PreprocessorDefinitions) を介する形で追加される。実行効果的としては Win32 API や _T マクロが W になったりする。 yes
NDEBUG no yes ( Release ) デバッグビルドではない=リリースビルドを意味する識別用の定義。プロジェクト作成時に Configuration -> C/C++ -> Preprocessor -> Preprocessor に明示的に追記される。 yes
WIN32 no yes ( x86 ) Windows の 32-Bit 版向けを意味する識別用の定義。プロジェクト作成時に Configuration -> C/C++ -> Preprocessor -> Preprocessor に明示的に追記される。 (*4) nope, ... yup... (*4)
WIN64 no no WIN32に対応したWIN64の定義について何か仕様があるような気がしてユーザーが定義する事のある何か。 no
UNICODE yes ( msbuild + ps ) (*1) yes UNICODEの使用を意味する識別用に定義される。プロジェクト作成時に _UNICODE と一緒に同様に追加される。 no

(*1): プロジェクト作成後に構成のプリプロセッサーの定義から $(PreprocessorDefinitions) を削除または;区切りを忘れて無効となる書き換えを行ったりすると Character Set に Use Unicode Character Set を設定していても定義されなくなってしまうので少しそのあたりの取り扱いには注意が必要です。

(*2): この列における「効果」は Windows SDK で分岐に使われていたりする効果です。例えば WIN64Visual Studio が直接扱ったり Windows SDK にそれを使うコードが含まれたりはしていませんが、しばしばユーザーが独自に定義して使用している事はあります(たいてい_WIN64を使った方がよいとは言え実際にWIN64が使われているコードもしばしば遭遇します)。

(*3): 一見、 _WIN32_DEBUG のように predefined macro のような気配を持っていますが、違います。Visual Studio はプロジェクト作成時に _WINDOWS を構成へ自動的に追記(VS .net 以降 VS 2019 現在まで)しますが、実際のところ意味はありません(対象プラットフォームが Windowsソースコード#if する用途では _WIN32 が一般的に使われていますし、 predefined macro なのでそちらの方が安心です)。 Windows SDK では内部的に _WINDOWS_ というよく似た定義は使っていますが _WINDOWS は使っていません。

(4): VS2019 の時代では(おそらく)完全に MSDN などの Microsoft 公式の情報がオンライン上に現存しないアンドキュメント化&実行効果的にもおまじない化したゆるふわ CPP 定義のようにも思え…ますが、実は現行の Windows SDK の一部でも実際に使われているので、 Win32 API の一部は WIN32 を明示的に追加定義してビルドしないと期待動作しない可能性があります。具体的に言うと WinSock2.h を使いたい場合とか。WIN32 が未定義の場合、 Windows SDK の一部のヘッダーでは 16-Bit 時代の Windows 向けにコードが分岐したりします。ごく一部の機能を使わなければ WIN32 を定義しなくても現行の Windows SDK のほとんどの機能は問題無く使えます。しかし、執筆現在の時代では、公式な資料がオンライン上に現存 しません(たぶん)(5)。

(*5): 著者の記憶からこの定義の存在について歴史的経緯を思い出してみると、 Visual C++ 6.0 かもう少し後くらいの時代に、それまで WIN32 を使う事になっていた"お約束"をMicrosoftが「これからは_WIN32を使おうよ」と変更した…とかあったような気がしないでもないような…どうだったかな…的な何かです。そういえばもうずいぶん昔、 cygwinmingw の環境から winsock2 を使う必要があったときにちょっとだけ WIN32 に "おこ" した気もしますが…もうほぼ思い出せる記憶はありません…。

WIN32 に関する明確な仕様や歴史的経緯のわかる資料についての情報提供の呼びかけ

私が C++ を"当時のドシロウトなり"でもそれなりに使うようになったのは実質的には Microsoft Visual C++ 6.0 からです。それ以前は Microsoft Visual Basic 4.0 から 6.0 時代までの旧式(.netではない)の Visual BasicWindowsGUI アプリを作っていました。処理速度の限界に耐えられなくなり、 C++ デビューした昔ばなし…はまた機会があればメモを残す事にします。

当時は C++ の言語仕様と VC++ 6.0 の言語標準からのずれがどうこうとごちゃごちゃ言えるほどの知識も無く、CD-ROMで配布されていた MSDN (当時のMSDNは翻訳クオリティーがまともだったので日本語でも読みやすかったし、謎のリンク切れも無く快適だったのです…)が教科書みたいなものでした。

そのような次第なので、私の当時やそれ以前の C++VC++ に関する知識は存在しないか、後付けで勉強したか、または想像が記憶を捏造してしまっている可能性も十分にあります。それで、今回気になったために ↑ のような情報提供の呼びかけをふんわりしてみたのでした。

追記: このメモはなんなの?

VS2019でC++プロジェクトを扱う際に、MSVC++での開発で頻出するCPPマクロ定義について、"じっさい"のところ構成などで明示的に定義する必要がある/ないを判断する参考用です。構成が多数あり複雑になりがちな場合に少しでも保守コストを軽減したい、そんなときに何が要らないか気になったので整理しました。