11
11
#ifndef __WIL_COM_APARTMENT_VARIABLE_INCLUDED
12
12
#define __WIL_COM_APARTMENT_VARIABLE_INCLUDED
13
13
14
- #include < unordered_map>
15
14
#include < any>
15
+ #include < objidl.h>
16
+ #include < roapi.h>
16
17
#include < type_traits>
18
+ #include < unordered_map>
19
+ #include < winrt/Windows.Foundation.h>
20
+
17
21
#include " com.h"
18
22
#include " cppwinrt.h"
19
- #include < roapi.h>
20
- #include < objidl.h>
21
23
#include " result_macros.h"
22
- #include < winrt/Windows.Foundation.h >
24
+ #include " win32_helpers.h "
23
25
24
26
#ifndef WIL_ENABLE_EXCEPTIONS
25
27
#error This header requires exceptions
@@ -75,6 +77,20 @@ namespace wil
75
77
using shutdown_type = wil::unique_apartment_shutdown_registration;
76
78
};
77
79
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
+
78
94
namespace details
79
95
{
80
96
// 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
117
133
}
118
134
};
119
135
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>
121
138
struct apartment_variable_base
122
139
{
123
140
inline static winrt::slim_mutex s_lock;
@@ -133,25 +150,54 @@ namespace wil
133
150
134
151
winrt::apartment_context context;
135
152
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;
137
155
};
138
156
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;
142
161
143
- apartment_variable_base () = default ;
162
+ constexpr apartment_variable_base () = default;
144
163
~apartment_variable_base ()
145
164
{
146
165
// Global variables (object with static storage duration)
147
166
// are run down when the process is shutting down or when the
148
167
// dll is unloaded. At these points it is not possible to start
149
168
// an async operation and the work performed is not needed,
150
169
// the apartments with variable have been run down already.
151
- if (!details::IsGlobalVariable (this ))
170
+ const auto isGlobal = details::IsGlobalVariable (this );
171
+ if (!isGlobal)
152
172
{
153
173
clear_all_apartments_async ();
154
174
}
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
+ }
155
201
}
156
202
157
203
// non-copyable, non-assignable
@@ -170,8 +216,8 @@ namespace wil
170
216
171
217
static apartment_variable_storage* get_current_apartment_variable_storage ()
172
218
{
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 ())
175
221
{
176
222
return &storage->second ;
177
223
}
@@ -195,7 +241,7 @@ namespace wil
195
241
auto variables = [apartmentId]()
196
242
{
197
243
auto lock = winrt::slim_lock_guard (s_lock);
198
- return s_apartmentStorage.extract (apartmentId);
244
+ return s_apartmentStorage.get (). extract (apartmentId);
199
245
}();
200
246
WI_ASSERT (variables.key () == apartmentId);
201
247
// The system implicitly releases the shutdown observer
@@ -205,12 +251,12 @@ namespace wil
205
251
}
206
252
};
207
253
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 ;
209
255
}
210
256
211
257
// get current value or custom-construct one on demand
212
258
template <typename T>
213
- std::any& get_or_create (any_maker<T>&& creator)
259
+ std::any& get_or_create (any_maker<T> && creator)
214
260
{
215
261
apartment_variable_storage* variable_storage = nullptr ;
216
262
@@ -256,8 +302,8 @@ namespace wil
256
302
// release value, with the swapped value, outside of the lock
257
303
{
258
304
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 ());
261
307
auto & variable_storage = storage->second ;
262
308
auto variable = variable_storage.variables .find (this );
263
309
FAIL_FAST_IF (variable == variable_storage.variables .end ());
@@ -274,7 +320,7 @@ namespace wil
274
320
variable_storage->variables .erase (this );
275
321
if (variable_storage->variables .size () == 0 )
276
322
{
277
- s_apartmentStorage.erase (test_hook::GetApartmentId ());
323
+ s_apartmentStorage.get (). erase (test_hook::GetApartmentId ());
278
324
}
279
325
}
280
326
}
@@ -289,7 +335,7 @@ namespace wil
289
335
std::vector<winrt::apartment_context> contexts;
290
336
{ // scope for lock
291
337
auto lock = winrt::slim_lock_guard (s_lock);
292
- for (auto & [id, storage] : s_apartmentStorage)
338
+ for (auto & [id, storage] : s_apartmentStorage. get () )
293
339
{
294
340
auto variable = storage.variables .find (this );
295
341
if (variable != storage.variables .end ())
@@ -344,7 +390,7 @@ namespace wil
344
390
345
391
static const auto & storage ()
346
392
{
347
- return s_apartmentStorage;
393
+ return s_apartmentStorage. get () ;
348
394
}
349
395
350
396
static size_t current_apartment_variable_count ()
@@ -372,10 +418,13 @@ namespace wil
372
418
// C++ WinRT objects. This is automatic for DLLs that host C++ WinRT objects
373
419
// but WRL projects will need to be updated to call winrt::get_module_lock().
374
420
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>
377
424
{
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;
379
428
380
429
// Get current value or throw if no value has been set.
381
430
T& get_existing () { return std::any_cast<T&>(base::get_existing ()); }
0 commit comments