Shutdown aware objects - microsoft/wil GitHub Wiki
The WIL error handling helpers include helpers which permit objects to change behavior during process shutdown, usually by bypassing unnecessary work.
All of the functions and types are in the wil namespace.
Note that these helpers are required only in DLLs, because executables cannot be unloaded dynamically. Any destructors in executables necessarily run at process shutdown.
Functions
ProcessShutdownInProgress
bool wil::ProcessShutdownInProgress();
This function is the basis for the shutdown-aware objects.
It returns true if the process is shutting down.
You are responsible for calling wil::DLLMain (below)
in your DllMain function's DLL_PROCESS_DETACH handler
in order to inform WIL that the process is shutting down.
Since executables do not receive a
DLL_PROCESS_DETACH notification,
this function is not effective for WIL objects
in executables.
However, there is no need for shutdown-aware objects
in executables because executables cannot be
unloaded dynamically.
DLLMain
void wil::DLLMain(HINSTANCE, DWORD reason, void* reserved);
Your DLLMain function must forward all calls
to the wil::DLLMain function so that
WIL can become aware of process shutdown.
Example:
BOOL CALLBACK DllMain(HINSTANCE hinst, DWORD reason, void* reserved)
{
// let WIL know about process lifetime
wil::DLLMain(hinst, reason, reserved);
... normal DllMain code goes here ...
}
Note that the capitalization of the WIL DLLMain function
differs from the capitalization of the
conventional DllMain function.
Shutdown-aware objects
There are three types of shutdown-aware objects, with increasing complexity. They all wrap a T object.
wil::object_without_destructor_on_shutdown<T>constructs theTautomatically. During process shutdown, it leaks theTobject instead of destructing it.wil::shutdown_aware_object<T>constructs theTautomatically. During process shutdown, it calls a specialProcessShutdown()method on theTobject instead of destructing it.wil::manually_managed_shutdown_aware_object<T>constructs and destructs theTobject only when explicitly instructed. During process shutdown, it calls a specialProcessShutdown()method on theTobject instead of destructing it.
This table summarizes the behavior and requirements.
| Wrapper class | object_without_destructor_on_shutdown<T> |
shutdown_aware_object<T> |
manually_managed_shutdown_aware_object<T> |
|---|---|---|---|
Constructs the T |
Automatically | Automatically | When you call construct() |
Cleans up the T |
Automatically | Automatically | When you call destroy() |
| If process shutting down | Does nothing | Calls T::ProcessShutdown() |
Calls T::ProcessShutdown() |
| If process not shutting down | Destructs the T |
Destructs the T |
Destructs the T |
Constructibility of T |
Public default constructor | Public default constructor | Public default constructor |
Destructibility of T |
Public destructor | Public destructor | Public destructor |
| Other requirements | Public method void ProcessShutdown() |
Public method void ProcessShutdown() |
The intended usage is as follows:
| Wrapper class | object_without_destructor_on_shutdown<T> |
shutdown_aware_object<T> |
manually_managed_shutdown_aware_object<T> |
|---|---|---|---|
| Declaration | Declare a global variable | Declare a global variable | Declare a global variable |
In DLL_PROCESS_ATTACH |
Call construct() |
||
In DLL_PROCESS_DETACH |
Call destroy() |
||
| In your destructor | Clean up everything | Clean up everything | Clean up everything |
In your ProcessShutdown() |
N/A | Clean up minimal | Clean up minimal |
Note, since the T is leaked, use in DLLs that can unload results in a leak when unloaded. Only use these helpers if the DLL will never unload.
Minimal cleanup may consist of flushing lazy-written data.
In the case of a manually_managed_shutdown_aware_object,
construct() may be called
only when the object is in its empty/destroyed state,
and destroy() may be called only when the object is in the constructed state.
Double-construction or double-destruction results in undefined behavior.
All three template classes have the following member function:
T& get()
Returns a reference to the wrapped object.
Example:
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> m_usage;
};
wil::shutdown_aware_object<FeatureUsageData> featureUsageData;
This hypothetical class records feature usage statistics.
The usage statistics are cached in the m_usage map.
When the DLL unloads or the process terminates,
the usage statistics are saved by the hypothetical
SaveUsageData method.
In the case of process termination, we save the usage data,
but do not destruct the map,
thereby short-circuiting unnecessary work.