1111#ifndef __WIL_COM_APARTMENT_VARIABLE_INCLUDED
1212#define __WIL_COM_APARTMENT_VARIABLE_INCLUDED
1313
14- #include < unordered_map>
1514#include < any>
15+ #include < objidl.h>
16+ #include < roapi.h>
1617#include < type_traits>
18+ #include < unordered_map>
19+ #include < winrt/Windows.Foundation.h>
20+
1721#include " com.h"
1822#include " cppwinrt.h"
19- #include < roapi.h>
20- #include < objidl.h>
2123#include " result_macros.h"
22- #include < winrt/Windows.Foundation.h >
24+ #include " win32_helpers.h "
2325
2426#ifndef WIL_ENABLE_EXCEPTIONS
2527#error This header requires exceptions
@@ -75,6 +77,20 @@ namespace wil
7577 using shutdown_type = wil::unique_apartment_shutdown_registration;
7678 };
7779
80+ enum class apartment_variable_leak_action { fail_fast, ignore };
81+
82+ // "pins" the current module in memory by incrementing the module reference count and leaking that.
83+ inline void ensure_module_stays_loaded ()
84+ {
85+ static INIT_ONCE s_initLeakModule{}; // avoiding magic statics
86+ wil::init_once_failfast (s_initLeakModule, []()
87+ {
88+ HMODULE result{};
89+ FAIL_FAST_IF (!GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, L" " , &result));
90+ return S_OK;
91+ });
92+ }
93+
7894 namespace details
7995 {
8096 // For the address of data, you can detect global variables by the ability to resolve the module from the address.
@@ -117,7 +133,8 @@ namespace wil
117133 }
118134 };
119135
120- template <typename test_hook = apartment_variable_platform>
136+ template <apartment_variable_leak_action leak_action = apartment_variable_leak_action::fail_fast,
137+ typename test_hook = apartment_variable_platform>
121138 struct apartment_variable_base
122139 {
123140 inline static winrt::slim_mutex s_lock;
@@ -133,25 +150,54 @@ namespace wil
133150
134151 winrt::apartment_context context;
135152 typename test_hook::shutdown_type cookie;
136- std::unordered_map<apartment_variable_base<test_hook>*, std::any> variables;
153+ // Variables are stored using the address of the apartment_variable_base<> as the key.
154+ std::unordered_map<apartment_variable_base<leak_action, test_hook>*, std::any> variables;
137155 };
138156
139- // Apartment id -> variable storage.
140- // Variables are stored using the address of the global variable as the key.
141- inline static std::unordered_map<unsigned long long , apartment_variable_storage> s_apartmentStorage;
157+ // Apartment id -> variables storage.
158+ inline static wil::object_without_destructor_on_shutdown<
159+ std::unordered_map<unsigned long long , apartment_variable_storage>>
160+ s_apartmentStorage;
142161
143- apartment_variable_base () = default ;
162+ constexpr apartment_variable_base () = default;
144163 ~apartment_variable_base ()
145164 {
146165 // Global variables (object with static storage duration)
147166 // are run down when the process is shutting down or when the
148167 // dll is unloaded. At these points it is not possible to start
149168 // an async operation and the work performed is not needed,
150169 // the apartments with variable have been run down already.
151- if (!details::IsGlobalVariable (this ))
170+ const auto isGlobal = details::IsGlobalVariable (this );
171+ if (!isGlobal)
152172 {
153173 clear_all_apartments_async ();
154174 }
175+
176+ if constexpr (leak_action == apartment_variable_leak_action::fail_fast)
177+ {
178+ if (isGlobal && !ProcessShutdownInProgress ())
179+ {
180+ // If you hit this fail fast it means the storage in s_apartmentStorage will be leaked.
181+ // For apartment variables used in .exes, this is expected and
182+ // this fail fast should be disabled using
183+ // wil::apartment_variable<T, wil::apartment_variable_leak_action::ignore>
184+ //
185+ // For DLLs, if this is expected, disable this fail fast using
186+ // wil::apartment_variable<T, wil::apartment_variable_leak_action::ignore>
187+ //
188+ // Use of apartment variables in DLLs only loaded by COM will never hit this case
189+ // as COM will unload DLLs before apartments are rundown,
190+ // providing the opportunity to empty s_apartmentStorage.
191+ //
192+ // But DLLs loaded and unloaded to call DLL entry points (outside of COM) may
193+ // create variable storage that can't be cleaned up as the DLL lifetime is
194+ // shorter that the COM lifetime. In these cases either
195+ // 1) accept the leaks and disable the fail fast as describe above
196+ // 2) disable module unloading by calling wil::ensure_module_stays_loaded
197+ // 3) CoCreate an object from this DLL to make COM aware of the DLL
198+ FAIL_FAST_IF (!s_apartmentStorage.get ().empty ());
199+ }
200+ }
155201 }
156202
157203 // non-copyable, non-assignable
@@ -170,8 +216,8 @@ namespace wil
170216
171217 static apartment_variable_storage* get_current_apartment_variable_storage ()
172218 {
173- auto storage = s_apartmentStorage.find (test_hook::GetApartmentId ());
174- if (storage != s_apartmentStorage.end ())
219+ auto storage = s_apartmentStorage.get (). find (test_hook::GetApartmentId ());
220+ if (storage != s_apartmentStorage.get (). end ())
175221 {
176222 return &storage->second ;
177223 }
@@ -195,7 +241,7 @@ namespace wil
195241 auto variables = [apartmentId]()
196242 {
197243 auto lock = winrt::slim_lock_guard (s_lock);
198- return s_apartmentStorage.extract (apartmentId);
244+ return s_apartmentStorage.get (). extract (apartmentId);
199245 }();
200246 WI_ASSERT (variables.key () == apartmentId);
201247 // The system implicitly releases the shutdown observer
@@ -205,12 +251,12 @@ namespace wil
205251 }
206252 };
207253 auto shutdownRegistration = test_hook::RegisterForApartmentShutdown (winrt::make<ApartmentObserver>().get ());
208- return &s_apartmentStorage.insert ({ test_hook::GetApartmentId (), apartment_variable_storage (std::move (shutdownRegistration)) }).first ->second ;
254+ return &s_apartmentStorage.get (). insert ({ test_hook::GetApartmentId (), apartment_variable_storage (std::move (shutdownRegistration)) }).first ->second ;
209255 }
210256
211257 // get current value or custom-construct one on demand
212258 template <typename T>
213- std::any& get_or_create (any_maker<T>&& creator)
259+ std::any& get_or_create (any_maker<T> && creator)
214260 {
215261 apartment_variable_storage* variable_storage = nullptr ;
216262
@@ -256,8 +302,8 @@ namespace wil
256302 // release value, with the swapped value, outside of the lock
257303 {
258304 auto lock = winrt::slim_lock_guard (s_lock);
259- auto storage = s_apartmentStorage.find (test_hook::GetApartmentId ());
260- FAIL_FAST_IF (storage == s_apartmentStorage.end ());
305+ auto storage = s_apartmentStorage.get (). find (test_hook::GetApartmentId ());
306+ FAIL_FAST_IF (storage == s_apartmentStorage.get (). end ());
261307 auto & variable_storage = storage->second ;
262308 auto variable = variable_storage.variables .find (this );
263309 FAIL_FAST_IF (variable == variable_storage.variables .end ());
@@ -274,7 +320,7 @@ namespace wil
274320 variable_storage->variables .erase (this );
275321 if (variable_storage->variables .size () == 0 )
276322 {
277- s_apartmentStorage.erase (test_hook::GetApartmentId ());
323+ s_apartmentStorage.get (). erase (test_hook::GetApartmentId ());
278324 }
279325 }
280326 }
@@ -289,7 +335,7 @@ namespace wil
289335 std::vector<winrt::apartment_context> contexts;
290336 { // scope for lock
291337 auto lock = winrt::slim_lock_guard (s_lock);
292- for (auto & [id, storage] : s_apartmentStorage)
338+ for (auto & [id, storage] : s_apartmentStorage. get () )
293339 {
294340 auto variable = storage.variables .find (this );
295341 if (variable != storage.variables .end ())
@@ -344,7 +390,7 @@ namespace wil
344390
345391 static const auto & storage ()
346392 {
347- return s_apartmentStorage;
393+ return s_apartmentStorage. get () ;
348394 }
349395
350396 static size_t current_apartment_variable_count ()
@@ -372,10 +418,13 @@ namespace wil
372418 // C++ WinRT objects. This is automatic for DLLs that host C++ WinRT objects
373419 // but WRL projects will need to be updated to call winrt::get_module_lock().
374420
375- template <typename T, typename test_hook = wil::apartment_variable_platform>
376- struct apartment_variable : details::apartment_variable_base<test_hook>
421+ template <typename T, apartment_variable_leak_action leak_action = apartment_variable_leak_action::fail_fast,
422+ typename test_hook = wil::apartment_variable_platform>
423+ struct apartment_variable : details::apartment_variable_base<leak_action, test_hook>
377424 {
378- using base = details::apartment_variable_base<test_hook>;
425+ using base = details::apartment_variable_base<leak_action, test_hook>;
426+
427+ constexpr apartment_variable () = default;
379428
380429 // Get current value or throw if no value has been set.
381430 T& get_existing () { return std::any_cast<T&>(base::get_existing ()); }
0 commit comments