com_apartment_variable.h 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. //*********************************************************
  2. //
  3. // Copyright (c) Microsoft. All rights reserved.
  4. // This code is licensed under the MIT License.
  5. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
  6. // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  7. // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  8. // PARTICULAR PURPOSE AND NONINFRINGEMENT.
  9. //
  10. //*********************************************************
  11. #ifndef __WIL_COM_APARTMENT_VARIABLE_INCLUDED
  12. #define __WIL_COM_APARTMENT_VARIABLE_INCLUDED
  13. #include <any>
  14. #include <objidl.h>
  15. #include <roapi.h>
  16. #include <type_traits>
  17. #include <unordered_map>
  18. #include <winrt/Windows.Foundation.h>
  19. #include "com.h"
  20. #include "cppwinrt.h"
  21. #include "result_macros.h"
  22. #include "win32_helpers.h"
  23. #ifndef WIL_ENABLE_EXCEPTIONS
  24. #error This header requires exceptions
  25. #endif
  26. namespace wil
  27. {
  28. // Determine if apartment variables are supported in the current process context.
  29. // Prior to build 22365, the APIs needed to create apartment variables (e.g. RoGetApartmentIdentifier)
  30. // failed for unpackaged processes. For MS people, see http://task.ms/31861017 for details.
  31. // APIs needed to implement apartment variables did not work in non-packaged processes.
  32. inline bool are_apartment_variables_supported()
  33. {
  34. unsigned long long apartmentId{};
  35. return RoGetApartmentIdentifier(&apartmentId) != HRESULT_FROM_WIN32(ERROR_API_UNAVAILABLE);
  36. }
  37. // COM will implicitly rundown the apartment registration when it invokes a handler
  38. // and blocks calling unregister when executing the callback. So be careful to release()
  39. // this when callback is invoked to avoid a double free of the cookie.
  40. using unique_apartment_shutdown_registration = unique_any<APARTMENT_SHUTDOWN_REGISTRATION_COOKIE, decltype(&::RoUnregisterForApartmentShutdown), ::RoUnregisterForApartmentShutdown>;
  41. struct apartment_variable_platform
  42. {
  43. static unsigned long long GetApartmentId()
  44. {
  45. unsigned long long apartmentId{};
  46. FAIL_FAST_IF_FAILED(RoGetApartmentIdentifier(&apartmentId));
  47. return apartmentId;
  48. }
  49. static auto RegisterForApartmentShutdown(IApartmentShutdown* observer)
  50. {
  51. unsigned long long id{};
  52. shutdown_type cookie;
  53. THROW_IF_FAILED(RoRegisterForApartmentShutdown(observer, &id, cookie.put()));
  54. return cookie;
  55. }
  56. static void UnRegisterForApartmentShutdown(APARTMENT_SHUTDOWN_REGISTRATION_COOKIE cookie)
  57. {
  58. FAIL_FAST_IF_FAILED(RoUnregisterForApartmentShutdown(cookie));
  59. }
  60. static auto CoInitializeEx(DWORD coinitFlags = 0 /*COINIT_MULTITHREADED*/)
  61. {
  62. return wil::CoInitializeEx(coinitFlags);
  63. }
  64. // disable the test hook
  65. inline static constexpr unsigned long AsyncRundownDelayForTestingRaces = INFINITE;
  66. using shutdown_type = wil::unique_apartment_shutdown_registration;
  67. };
  68. enum class apartment_variable_leak_action { fail_fast, ignore };
  69. // "pins" the current module in memory by incrementing the module reference count and leaking that.
  70. inline void ensure_module_stays_loaded()
  71. {
  72. static INIT_ONCE s_initLeakModule{}; // avoiding magic statics
  73. wil::init_once_failfast(s_initLeakModule, []()
  74. {
  75. HMODULE result{};
  76. FAIL_FAST_IF(!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, L"", &result));
  77. return S_OK;
  78. });
  79. }
  80. namespace details
  81. {
  82. // For the address of data, you can detect global variables by the ability to resolve the module from the address.
  83. inline bool IsGlobalVariable(const void* moduleAddress) noexcept
  84. {
  85. wil::unique_hmodule moduleHandle;
  86. return GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<PCWSTR>(moduleAddress), &moduleHandle) != FALSE;
  87. }
  88. struct any_maker_base
  89. {
  90. std::any(*adapter)(void*);
  91. void* inner;
  92. std::any operator()() const
  93. {
  94. return adapter(inner);
  95. }
  96. };
  97. template<typename T>
  98. struct any_maker : any_maker_base
  99. {
  100. any_maker()
  101. {
  102. adapter = [](auto) -> std::any { return T{}; };
  103. }
  104. any_maker(T(*maker)())
  105. {
  106. adapter = [](auto maker) -> std::any { return reinterpret_cast<T(*)()>(maker)(); };
  107. inner = reinterpret_cast<void*>(maker);
  108. }
  109. template<typename F>
  110. any_maker(F&& f)
  111. {
  112. adapter = [](auto maker) -> std::any { return reinterpret_cast<F*>(maker)[0](); };
  113. inner = std::addressof(f);
  114. }
  115. };
  116. template<apartment_variable_leak_action leak_action = apartment_variable_leak_action::fail_fast,
  117. typename test_hook = apartment_variable_platform>
  118. struct apartment_variable_base
  119. {
  120. inline static winrt::slim_mutex s_lock;
  121. struct apartment_variable_storage
  122. {
  123. apartment_variable_storage(apartment_variable_storage&& other) noexcept = default;
  124. apartment_variable_storage(const apartment_variable_storage& other) = delete;
  125. apartment_variable_storage(typename test_hook::shutdown_type&& cookie_) : cookie(std::move(cookie_))
  126. {
  127. }
  128. winrt::apartment_context context;
  129. typename test_hook::shutdown_type cookie;
  130. // Variables are stored using the address of the apartment_variable_base<> as the key.
  131. std::unordered_map<apartment_variable_base<leak_action, test_hook>*, std::any> variables;
  132. };
  133. // Apartment id -> variables storage.
  134. inline static wil::object_without_destructor_on_shutdown<
  135. std::unordered_map<unsigned long long, apartment_variable_storage>>
  136. s_apartmentStorage;
  137. constexpr apartment_variable_base() = default;
  138. ~apartment_variable_base()
  139. {
  140. // Global variables (object with static storage duration)
  141. // are run down when the process is shutting down or when the
  142. // dll is unloaded. At these points it is not possible to start
  143. // an async operation and the work performed is not needed,
  144. // the apartments with variable have been run down already.
  145. const auto isGlobal = details::IsGlobalVariable(this);
  146. if (!isGlobal)
  147. {
  148. clear_all_apartments_async();
  149. }
  150. if constexpr (leak_action == apartment_variable_leak_action::fail_fast)
  151. {
  152. if (isGlobal && !ProcessShutdownInProgress())
  153. {
  154. // If you hit this fail fast it means the storage in s_apartmentStorage will be leaked.
  155. // For apartment variables used in .exes, this is expected and
  156. // this fail fast should be disabled using
  157. // wil::apartment_variable<T, wil::apartment_variable_leak_action::ignore>
  158. //
  159. // For DLLs, if this is expected, disable this fail fast using
  160. // wil::apartment_variable<T, wil::apartment_variable_leak_action::ignore>
  161. //
  162. // Use of apartment variables in DLLs only loaded by COM will never hit this case
  163. // as COM will unload DLLs before apartments are rundown,
  164. // providing the opportunity to empty s_apartmentStorage.
  165. //
  166. // But DLLs loaded and unloaded to call DLL entry points (outside of COM) may
  167. // create variable storage that can't be cleaned up as the DLL lifetime is
  168. // shorter that the COM lifetime. In these cases either
  169. // 1) accept the leaks and disable the fail fast as describe above
  170. // 2) disable module unloading by calling wil::ensure_module_stays_loaded
  171. // 3) CoCreate an object from this DLL to make COM aware of the DLL
  172. FAIL_FAST_IF(!s_apartmentStorage.get().empty());
  173. }
  174. }
  175. }
  176. // non-copyable, non-assignable
  177. apartment_variable_base(apartment_variable_base const&) = delete;
  178. void operator=(apartment_variable_base const&) = delete;
  179. // get current value or throw if no value has been set
  180. std::any& get_existing()
  181. {
  182. if (auto any = get_if())
  183. {
  184. return *any;
  185. }
  186. THROW_HR(E_NOT_SET);
  187. }
  188. static apartment_variable_storage* get_current_apartment_variable_storage()
  189. {
  190. auto storage = s_apartmentStorage.get().find(test_hook::GetApartmentId());
  191. if (storage != s_apartmentStorage.get().end())
  192. {
  193. return &storage->second;
  194. }
  195. return nullptr;
  196. }
  197. apartment_variable_storage* ensure_current_apartment_variables()
  198. {
  199. auto variables = get_current_apartment_variable_storage();
  200. if (variables)
  201. {
  202. return variables;
  203. }
  204. struct ApartmentObserver : public winrt::implements<ApartmentObserver, IApartmentShutdown>
  205. {
  206. void STDMETHODCALLTYPE OnUninitialize(unsigned long long apartmentId) noexcept override
  207. {
  208. // This code runs at apartment rundown so be careful to avoid deadlocks by
  209. // extracting the variables under the lock then release them outside.
  210. auto variables = [apartmentId]()
  211. {
  212. auto lock = winrt::slim_lock_guard(s_lock);
  213. return s_apartmentStorage.get().extract(apartmentId);
  214. }();
  215. WI_ASSERT(variables.key() == apartmentId);
  216. // The system implicitly releases the shutdown observer
  217. // after invoking the callback and does not allow calling unregister
  218. // in the callback. So release the reference to the registration.
  219. variables.mapped().cookie.release();
  220. }
  221. };
  222. auto shutdownRegistration = test_hook::RegisterForApartmentShutdown(winrt::make<ApartmentObserver>().get());
  223. return &s_apartmentStorage.get().insert({ test_hook::GetApartmentId(), apartment_variable_storage(std::move(shutdownRegistration)) }).first->second;
  224. }
  225. // get current value or custom-construct one on demand
  226. template<typename T>
  227. std::any& get_or_create(any_maker<T> && creator)
  228. {
  229. apartment_variable_storage* variable_storage = nullptr;
  230. { // scope for lock
  231. auto lock = winrt::slim_lock_guard(s_lock);
  232. variable_storage = ensure_current_apartment_variables();
  233. auto variable = variable_storage->variables.find(this);
  234. if (variable != variable_storage->variables.end())
  235. {
  236. return variable->second;
  237. }
  238. } // drop the lock
  239. // create the object outside the lock to avoid reentrant deadlock
  240. auto value = creator();
  241. auto insert_lock = winrt::slim_lock_guard(s_lock);
  242. // The insertion may fail if creator() recursively caused itself to be created,
  243. // in which case we return the existing object and the falsely-created one is discarded.
  244. return variable_storage->variables.insert({ this, std::move(value) }).first->second;
  245. }
  246. // get pointer to current value or nullptr if no value has been set
  247. std::any* get_if()
  248. {
  249. auto lock = winrt::slim_lock_guard(s_lock);
  250. if (auto variable_storage = get_current_apartment_variable_storage())
  251. {
  252. auto variable = variable_storage->variables.find(this);
  253. if (variable != variable_storage->variables.end())
  254. {
  255. return &(variable->second);
  256. }
  257. }
  258. return nullptr;
  259. }
  260. // replace or create the current value, fail fasts if the value is not already stored
  261. void set(std::any value)
  262. {
  263. // release value, with the swapped value, outside of the lock
  264. {
  265. auto lock = winrt::slim_lock_guard(s_lock);
  266. auto storage = s_apartmentStorage.get().find(test_hook::GetApartmentId());
  267. FAIL_FAST_IF(storage == s_apartmentStorage.get().end());
  268. auto& variable_storage = storage->second;
  269. auto variable = variable_storage.variables.find(this);
  270. FAIL_FAST_IF(variable == variable_storage.variables.end());
  271. variable->second.swap(value);
  272. }
  273. }
  274. // remove any current value
  275. void clear()
  276. {
  277. auto lock = winrt::slim_lock_guard(s_lock);
  278. if (auto variable_storage = get_current_apartment_variable_storage())
  279. {
  280. variable_storage->variables.erase(this);
  281. if (variable_storage->variables.size() == 0)
  282. {
  283. s_apartmentStorage.get().erase(test_hook::GetApartmentId());
  284. }
  285. }
  286. }
  287. winrt::Windows::Foundation::IAsyncAction clear_all_apartments_async()
  288. {
  289. // gather all the apartments that hold objects we need to destruct
  290. // (do not gather the objects themselves, because the apartment might
  291. // destruct before we get around to it, and we should let the apartment
  292. // destruct the object while it still can).
  293. std::vector<winrt::apartment_context> contexts;
  294. { // scope for lock
  295. auto lock = winrt::slim_lock_guard(s_lock);
  296. for (auto& [id, storage] : s_apartmentStorage.get())
  297. {
  298. auto variable = storage.variables.find(this);
  299. if (variable != storage.variables.end())
  300. {
  301. contexts.push_back(storage.context);
  302. }
  303. }
  304. }
  305. if (contexts.empty())
  306. {
  307. co_return;
  308. }
  309. wil::unique_mta_usage_cookie mta_reference; // need to extend the MTA due to async cleanup
  310. FAIL_FAST_IF_FAILED(CoIncrementMTAUsage(mta_reference.put()));
  311. // From a background thread hop into each apartment to run down the object
  312. // if it's still there.
  313. co_await winrt::resume_background();
  314. // This hook enables testing the case where execution of this method loses the race with
  315. // apartment rundown by other means.
  316. if constexpr (test_hook::AsyncRundownDelayForTestingRaces != INFINITE)
  317. {
  318. Sleep(test_hook::AsyncRundownDelayForTestingRaces);
  319. }
  320. for (auto&& context : contexts)
  321. {
  322. try
  323. {
  324. co_await context;
  325. clear();
  326. }
  327. catch (winrt::hresult_error const& e)
  328. {
  329. // Ignore failure if apartment ran down before we could clean it up.
  330. // The object already ran down as part of apartment cleanup.
  331. if ((e.code() != RPC_E_SERVER_DIED_DNE) &&
  332. (e.code() != RPC_E_DISCONNECTED))
  333. {
  334. throw;
  335. }
  336. }
  337. catch (...)
  338. {
  339. FAIL_FAST();
  340. }
  341. }
  342. }
  343. static const auto& storage()
  344. {
  345. return s_apartmentStorage.get();
  346. }
  347. static size_t current_apartment_variable_count()
  348. {
  349. auto lock = winrt::slim_lock_guard(s_lock);
  350. if (auto variable_storage = get_current_apartment_variable_storage())
  351. {
  352. return variable_storage->variables.size();
  353. }
  354. return 0;
  355. }
  356. };
  357. }
  358. // Apartment variables enable storing COM objects safely in globals
  359. // (objects with static storage duration) by creating a unique copy
  360. // in each apartment and managing their lifetime based on apartment rundown
  361. // notifications.
  362. // They can also be used for automatic or dynamic storage duration but those
  363. // cases are less common.
  364. // This type is also useful for storing references to apartment affine objects.
  365. //
  366. // Note, that apartment variables hosted in a COM DLL need to integrate with
  367. // the DllCanUnloadNow() function to include the ref counts contributed by
  368. // C++ WinRT objects. This is automatic for DLLs that host C++ WinRT objects
  369. // but WRL projects will need to be updated to call winrt::get_module_lock().
  370. template<typename T, apartment_variable_leak_action leak_action = apartment_variable_leak_action::fail_fast,
  371. typename test_hook = wil::apartment_variable_platform>
  372. struct apartment_variable : details::apartment_variable_base<leak_action, test_hook>
  373. {
  374. using base = details::apartment_variable_base<leak_action, test_hook>;
  375. constexpr apartment_variable() = default;
  376. // Get current value or throw if no value has been set.
  377. T& get_existing() { return std::any_cast<T&>(base::get_existing()); }
  378. // Get current value or default-construct one on demand.
  379. T& get_or_create()
  380. {
  381. return std::any_cast<T&>(base::get_or_create(details::any_maker<T>()));
  382. }
  383. // Get current value or custom-construct one on demand.
  384. template<typename F>
  385. T& get_or_create(F&& f)
  386. {
  387. return std::any_cast<T&>(base::get_or_create(details::any_maker<T>(std::forward<F>(f))));
  388. }
  389. // get pointer to current value or nullptr if no value has been set
  390. T* get_if() { return std::any_cast<T>(base::get_if()); }
  391. // replace or create the current value, fail fasts if the value is not already stored
  392. template<typename V> void set(V&& value) { return base::set(std::forward<V>(value)); }
  393. // Clear the value in the current apartment.
  394. using base::clear;
  395. // Asynchronously clear the value in all apartments it is present in.
  396. using base::clear_all_apartments_async;
  397. // For testing only.
  398. // 1) To observe the state of the storage in the debugger assign this to
  399. // a temporary variable (const&) and watch its contents.
  400. // 2) Use this to test the implementation.
  401. using base::storage;
  402. // For testing only. The number of variables in the current apartment.
  403. using base::current_apartment_variable_count;
  404. };
  405. }
  406. #endif // __WIL_COM_APARTMENT_VARIABLE_INCLUDED