potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

WIL GitHub Wiki シャットダウン認識オブジェクトの和訳・改変

MicrosoftがGitHubでMITライセンス公開しているWILのGitHub Wikiの記事を和訳・改変したものです。素人による翻訳なので、誤訳や著作権上の問題などありましたらご連絡いただけますと幸いです。

シャットダウン認識オブジェクト(原題:Shutdown aware objects)

Chris Guzak、2021/12/11、リビジョン3個

WILのエラーハンドルヘルパーはプロセスのシャットダウン中に動作の変更を許可するヘルパーが含まれます。通常は不要な動作のスキップに使用されます。

全ての関数と型はwil名前空間内にあります。

これらのヘルパーはDLLのみで必要とされることに注意してください。実行ファイルは動的にアンロードされないためです。実行ファイルの任意のデストラクタはプロセスのシャットダウン時に実行される必要があります。

関数

ProcessShutdownInProgress

bool wil::ProcessShutdownInProgress();

この関数はシャットダウン認識オブジェクトの基礎となります。プロセスのシャットダウン中にtrueを返します。

WILにプロセスのシャットダウン中を知らせるためにDllMain関数のDLL_PROCESS_DETACHハンドラでwil::DLLMain(後述)を呼び出す必要があります。

実行ファイルはDLL_PROCESS_DETACH通知を受け取らないため、この関数は実行ファイルのWILオブジェクトには無効です。しかし、実行ファイルは動的アンロードできないため、シャットダウン認識オブジェクトは不要です。

DLLMain

void wil::DLLMain(HINSTANCE, DWORD reason, void* reserved);

DllMain関数はwil::DLLMain関数に全て転送してください。WILのプロセスシャットダウン認識に必要です。

具体例:

BOOL CALLBACK DllMain(HINSTANCE hinst, DWORD reason, void* reserved)
{
    // WILにプロセスのライフタイムを知らせる。
    wil::DLLMain(hinst, reason, reserved);

    ... 通常のDllMainコードをここへ記述します。 ...
}

WILのDLLMain関数は通常のDllMain関数と大文字小文字が異なることに注意してください。

シャットダウン認識オブジェクト

3種類のシャットダウン認識オブジェクトがあり、複雑です。いずれもTオブジェクトをラップします。

  • wil::object_without_destructor_on_shutdown<T>は自動的にTを作成します。プロセスのシャットダウン中、Tをデストラクトせずにリークさせます。
  • wil::shutdown_aware_object<T>は自動的にTを作成します。プロセスのシャットダウン中、Tをデストラクトさずに特殊なProcessShutdownメソッドを呼び出します。
  • wil::manually_managed_shutdown_aware_object<T>は明示に構築及び破壊されます。プロセスのシャットダウン中、Tをデストラクトせずに特殊なProcessShutdownメソッドを呼び出します。

上記の動作と必要条件を表で示します。

ラッパークラス object_without_
destructor_on_
shutdown<T>
shutdown_aware_
object<T>
manually_managed_
shutdown_aware
_object<T>
Tの構築 自動 自動 construct()呼び出し時
Tの破壊 自動 自動 destroy()呼び出し時
プロセスのシャットダウン中 何もしない T::ProcessShutdown() T::ProcessShutdown()
プロセスのシャットダウン中以外 Tの破壊 Tの破壊 Tの破壊
Tの構築 public既定コンストラク public既定コンストラク public既定コンストラク
Tの破壊 publicデストラク publicデストラク publicデストラク
他の必要条件 publicメソッドvoid ProcessShutdown() publicメソッドvoid ProcessShutdown()

用途を以下に示します。

ラッパークラス object_without_
destructor_on_
shutdown<T>
shutdown_aware_
object<T>
manually_managed_
shutdown_aware
_object<T>
宣言 グローバル変数として宣言 グローバル変数として宣言 グローバル変数として宣言
DLL_PROCESS_ATTACH construct()の呼び出し
DLL_PROCESS_DETACH destroy()の呼び出し
デストラク 全てクリーンアップ 全てクリーンアップ 全てクリーンアップ
ProcessShutdown() N/A 最小限のクリーンアップ 最小限のクリーンアップ

Tはリークされるため、アンロード可能なDLL中での使用はアンロード時のリークとなることに注意してください。これらのヘルパーはDLLが決してアンロードされない場合のみ使用してください。

最小限のクリーンアップは遅延書き込みデータのフラッシュ等からなるでしょう。

manually_managed_shutdown_aware_objectの場合、construct()はオブジェクトが空か破壊済みの場合のみ呼び出されるでしょう。また、destroy()はオブジェクトが構築済みの場合のみ呼び出されるでしょう。二重構築や二重破壊は未定義動作です。

3つのテンプレートクラスはいずれも以下のメンバー関数を持ちます。

  • T& get()
    ラップするオブジェクトの参照を返します。

具体例:

class FeatureUsageData
{
public:
    FeatureUsageData() = default;

    ~FeatureUsageData()
    {
        SaveUsageData();
    }

    void ProcessShutdown()
    {
        SaveUsageData();
    }

    void LogUsage(std::string const& feature)
    {
        auto guard = m_lock.lock_exclusive();
        ++m_usage[feature];
    }

private:
    wil::srwlock m_lock;
    std::map<std::string, int> _usage;
};

wil::shutdown_aware_object<FeatureUsageData> featureUsageData;

この架空のクラスは機能の使用状況統計を記録します。使用状況統計はm_usageマップにキャッシュされます。DLLがアンロードまたはプロセスが終了されるとき、使用状況統計は架空のSaveUsageDataメソッドにより保存されます。プロセスの終了時、使用状況データは保存されますが、マップは破壊されません。それにより、不必要な作業を短絡的に省けます。

著作権表示

この記事は以下の著作物を使用しています。

Copyright (c) Microsoft Corporation. All rights reserved. https://github.com/microsoft/wil/blob/master/LICENSE