123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- //*********************************************************
- //
- // Copyright (c) Microsoft. All rights reserved.
- // This code is licensed under the MIT License.
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
- // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
- // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- // PARTICULAR PURPOSE AND NONINFRINGEMENT.
- //
- //*********************************************************
- #ifndef __WIL_COM_APARTMENT_VARIABLE_INCLUDED
- #define __WIL_COM_APARTMENT_VARIABLE_INCLUDED
- #include <any>
- #include <objidl.h>
- #include <roapi.h>
- #include <type_traits>
- #include <unordered_map>
- #include <winrt/Windows.Foundation.h>
- #include "com.h"
- #include "cppwinrt.h"
- #include "result_macros.h"
- #include "win32_helpers.h"
- #ifndef WIL_ENABLE_EXCEPTIONS
- #error This header requires exceptions
- #endif
- namespace wil
- {
- // Determine if apartment variables are supported in the current process context.
- // Prior to build 22365, the APIs needed to create apartment variables (e.g. RoGetApartmentIdentifier)
- // failed for unpackaged processes. For MS people, see http://task.ms/31861017 for details.
- // APIs needed to implement apartment variables did not work in non-packaged processes.
- inline bool are_apartment_variables_supported()
- {
- unsigned long long apartmentId{};
- return RoGetApartmentIdentifier(&apartmentId) != HRESULT_FROM_WIN32(ERROR_API_UNAVAILABLE);
- }
- // COM will implicitly rundown the apartment registration when it invokes a handler
- // and blocks calling unregister when executing the callback. So be careful to release()
- // this when callback is invoked to avoid a double free of the cookie.
- using unique_apartment_shutdown_registration = unique_any<APARTMENT_SHUTDOWN_REGISTRATION_COOKIE, decltype(&::RoUnregisterForApartmentShutdown), ::RoUnregisterForApartmentShutdown>;
- struct apartment_variable_platform
- {
- static unsigned long long GetApartmentId()
- {
- unsigned long long apartmentId{};
- FAIL_FAST_IF_FAILED(RoGetApartmentIdentifier(&apartmentId));
- return apartmentId;
- }
- static auto RegisterForApartmentShutdown(IApartmentShutdown* observer)
- {
- unsigned long long id{};
- shutdown_type cookie;
- THROW_IF_FAILED(RoRegisterForApartmentShutdown(observer, &id, cookie.put()));
- return cookie;
- }
- static void UnRegisterForApartmentShutdown(APARTMENT_SHUTDOWN_REGISTRATION_COOKIE cookie)
- {
- FAIL_FAST_IF_FAILED(RoUnregisterForApartmentShutdown(cookie));
- }
- static auto CoInitializeEx(DWORD coinitFlags = 0 /*COINIT_MULTITHREADED*/)
- {
- return wil::CoInitializeEx(coinitFlags);
- }
- // disable the test hook
- inline static constexpr unsigned long AsyncRundownDelayForTestingRaces = INFINITE;
- using shutdown_type = wil::unique_apartment_shutdown_registration;
- };
- enum class apartment_variable_leak_action { fail_fast, ignore };
- // "pins" the current module in memory by incrementing the module reference count and leaking that.
- inline void ensure_module_stays_loaded()
- {
- static INIT_ONCE s_initLeakModule{}; // avoiding magic statics
- wil::init_once_failfast(s_initLeakModule, []()
- {
- HMODULE result{};
- FAIL_FAST_IF(!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, L"", &result));
- return S_OK;
- });
- }
- namespace details
- {
- // For the address of data, you can detect global variables by the ability to resolve the module from the address.
- inline bool IsGlobalVariable(const void* moduleAddress) noexcept
- {
- wil::unique_hmodule moduleHandle;
- return GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<PCWSTR>(moduleAddress), &moduleHandle) != FALSE;
- }
- struct any_maker_base
- {
- std::any(*adapter)(void*);
- void* inner;
- std::any operator()() const
- {
- return adapter(inner);
- }
- };
- template<typename T>
- struct any_maker : any_maker_base
- {
- any_maker()
- {
- adapter = [](auto) -> std::any { return T{}; };
- }
- any_maker(T(*maker)())
- {
- adapter = [](auto maker) -> std::any { return reinterpret_cast<T(*)()>(maker)(); };
- inner = reinterpret_cast<void*>(maker);
- }
- template<typename F>
- any_maker(F&& f)
- {
- adapter = [](auto maker) -> std::any { return reinterpret_cast<F*>(maker)[0](); };
- inner = std::addressof(f);
- }
- };
- template<apartment_variable_leak_action leak_action = apartment_variable_leak_action::fail_fast,
- typename test_hook = apartment_variable_platform>
- struct apartment_variable_base
- {
- inline static winrt::slim_mutex s_lock;
- struct apartment_variable_storage
- {
- apartment_variable_storage(apartment_variable_storage&& other) noexcept = default;
- apartment_variable_storage(const apartment_variable_storage& other) = delete;
- apartment_variable_storage(typename test_hook::shutdown_type&& cookie_) : cookie(std::move(cookie_))
- {
- }
- winrt::apartment_context context;
- typename test_hook::shutdown_type cookie;
- // Variables are stored using the address of the apartment_variable_base<> as the key.
- std::unordered_map<apartment_variable_base<leak_action, test_hook>*, std::any> variables;
- };
- // Apartment id -> variables storage.
- inline static wil::object_without_destructor_on_shutdown<
- std::unordered_map<unsigned long long, apartment_variable_storage>>
- s_apartmentStorage;
- constexpr apartment_variable_base() = default;
- ~apartment_variable_base()
- {
- // Global variables (object with static storage duration)
- // are run down when the process is shutting down or when the
- // dll is unloaded. At these points it is not possible to start
- // an async operation and the work performed is not needed,
- // the apartments with variable have been run down already.
- const auto isGlobal = details::IsGlobalVariable(this);
- if (!isGlobal)
- {
- clear_all_apartments_async();
- }
- if constexpr (leak_action == apartment_variable_leak_action::fail_fast)
- {
- if (isGlobal && !ProcessShutdownInProgress())
- {
- // If you hit this fail fast it means the storage in s_apartmentStorage will be leaked.
- // For apartment variables used in .exes, this is expected and
- // this fail fast should be disabled using
- // wil::apartment_variable<T, wil::apartment_variable_leak_action::ignore>
- //
- // For DLLs, if this is expected, disable this fail fast using
- // wil::apartment_variable<T, wil::apartment_variable_leak_action::ignore>
- //
- // Use of apartment variables in DLLs only loaded by COM will never hit this case
- // as COM will unload DLLs before apartments are rundown,
- // providing the opportunity to empty s_apartmentStorage.
- //
- // But DLLs loaded and unloaded to call DLL entry points (outside of COM) may
- // create variable storage that can't be cleaned up as the DLL lifetime is
- // shorter that the COM lifetime. In these cases either
- // 1) accept the leaks and disable the fail fast as describe above
- // 2) disable module unloading by calling wil::ensure_module_stays_loaded
- // 3) CoCreate an object from this DLL to make COM aware of the DLL
- FAIL_FAST_IF(!s_apartmentStorage.get().empty());
- }
- }
- }
- // non-copyable, non-assignable
- apartment_variable_base(apartment_variable_base const&) = delete;
- void operator=(apartment_variable_base const&) = delete;
- // get current value or throw if no value has been set
- std::any& get_existing()
- {
- if (auto any = get_if())
- {
- return *any;
- }
- THROW_HR(E_NOT_SET);
- }
- static apartment_variable_storage* get_current_apartment_variable_storage()
- {
- auto storage = s_apartmentStorage.get().find(test_hook::GetApartmentId());
- if (storage != s_apartmentStorage.get().end())
- {
- return &storage->second;
- }
- return nullptr;
- }
- apartment_variable_storage* ensure_current_apartment_variables()
- {
- auto variables = get_current_apartment_variable_storage();
- if (variables)
- {
- return variables;
- }
- struct ApartmentObserver : public winrt::implements<ApartmentObserver, IApartmentShutdown>
- {
- void STDMETHODCALLTYPE OnUninitialize(unsigned long long apartmentId) noexcept override
- {
- // This code runs at apartment rundown so be careful to avoid deadlocks by
- // extracting the variables under the lock then release them outside.
- auto variables = [apartmentId]()
- {
- auto lock = winrt::slim_lock_guard(s_lock);
- return s_apartmentStorage.get().extract(apartmentId);
- }();
- WI_ASSERT(variables.key() == apartmentId);
- // The system implicitly releases the shutdown observer
- // after invoking the callback and does not allow calling unregister
- // in the callback. So release the reference to the registration.
- variables.mapped().cookie.release();
- }
- };
- auto shutdownRegistration = test_hook::RegisterForApartmentShutdown(winrt::make<ApartmentObserver>().get());
- return &s_apartmentStorage.get().insert({ test_hook::GetApartmentId(), apartment_variable_storage(std::move(shutdownRegistration)) }).first->second;
- }
- // get current value or custom-construct one on demand
- template<typename T>
- std::any& get_or_create(any_maker<T> && creator)
- {
- apartment_variable_storage* variable_storage = nullptr;
- { // scope for lock
- auto lock = winrt::slim_lock_guard(s_lock);
- variable_storage = ensure_current_apartment_variables();
- auto variable = variable_storage->variables.find(this);
- if (variable != variable_storage->variables.end())
- {
- return variable->second;
- }
- } // drop the lock
- // create the object outside the lock to avoid reentrant deadlock
- auto value = creator();
- auto insert_lock = winrt::slim_lock_guard(s_lock);
- // The insertion may fail if creator() recursively caused itself to be created,
- // in which case we return the existing object and the falsely-created one is discarded.
- return variable_storage->variables.insert({ this, std::move(value) }).first->second;
- }
- // get pointer to current value or nullptr if no value has been set
- std::any* get_if()
- {
- auto lock = winrt::slim_lock_guard(s_lock);
- if (auto variable_storage = get_current_apartment_variable_storage())
- {
- auto variable = variable_storage->variables.find(this);
- if (variable != variable_storage->variables.end())
- {
- return &(variable->second);
- }
- }
- return nullptr;
- }
- // replace or create the current value, fail fasts if the value is not already stored
- void set(std::any value)
- {
- // release value, with the swapped value, outside of the lock
- {
- auto lock = winrt::slim_lock_guard(s_lock);
- auto storage = s_apartmentStorage.get().find(test_hook::GetApartmentId());
- FAIL_FAST_IF(storage == s_apartmentStorage.get().end());
- auto& variable_storage = storage->second;
- auto variable = variable_storage.variables.find(this);
- FAIL_FAST_IF(variable == variable_storage.variables.end());
- variable->second.swap(value);
- }
- }
- // remove any current value
- void clear()
- {
- auto lock = winrt::slim_lock_guard(s_lock);
- if (auto variable_storage = get_current_apartment_variable_storage())
- {
- variable_storage->variables.erase(this);
- if (variable_storage->variables.size() == 0)
- {
- s_apartmentStorage.get().erase(test_hook::GetApartmentId());
- }
- }
- }
- winrt::Windows::Foundation::IAsyncAction clear_all_apartments_async()
- {
- // gather all the apartments that hold objects we need to destruct
- // (do not gather the objects themselves, because the apartment might
- // destruct before we get around to it, and we should let the apartment
- // destruct the object while it still can).
- std::vector<winrt::apartment_context> contexts;
- { // scope for lock
- auto lock = winrt::slim_lock_guard(s_lock);
- for (auto& [id, storage] : s_apartmentStorage.get())
- {
- auto variable = storage.variables.find(this);
- if (variable != storage.variables.end())
- {
- contexts.push_back(storage.context);
- }
- }
- }
- if (contexts.empty())
- {
- co_return;
- }
- wil::unique_mta_usage_cookie mta_reference; // need to extend the MTA due to async cleanup
- FAIL_FAST_IF_FAILED(CoIncrementMTAUsage(mta_reference.put()));
- // From a background thread hop into each apartment to run down the object
- // if it's still there.
- co_await winrt::resume_background();
- // This hook enables testing the case where execution of this method loses the race with
- // apartment rundown by other means.
- if constexpr (test_hook::AsyncRundownDelayForTestingRaces != INFINITE)
- {
- Sleep(test_hook::AsyncRundownDelayForTestingRaces);
- }
- for (auto&& context : contexts)
- {
- try
- {
- co_await context;
- clear();
- }
- catch (winrt::hresult_error const& e)
- {
- // Ignore failure if apartment ran down before we could clean it up.
- // The object already ran down as part of apartment cleanup.
- if ((e.code() != RPC_E_SERVER_DIED_DNE) &&
- (e.code() != RPC_E_DISCONNECTED))
- {
- throw;
- }
- }
- catch (...)
- {
- FAIL_FAST();
- }
- }
- }
- static const auto& storage()
- {
- return s_apartmentStorage.get();
- }
- static size_t current_apartment_variable_count()
- {
- auto lock = winrt::slim_lock_guard(s_lock);
- if (auto variable_storage = get_current_apartment_variable_storage())
- {
- return variable_storage->variables.size();
- }
- return 0;
- }
- };
- }
- // Apartment variables enable storing COM objects safely in globals
- // (objects with static storage duration) by creating a unique copy
- // in each apartment and managing their lifetime based on apartment rundown
- // notifications.
- // They can also be used for automatic or dynamic storage duration but those
- // cases are less common.
- // This type is also useful for storing references to apartment affine objects.
- //
- // Note, that apartment variables hosted in a COM DLL need to integrate with
- // the DllCanUnloadNow() function to include the ref counts contributed by
- // C++ WinRT objects. This is automatic for DLLs that host C++ WinRT objects
- // but WRL projects will need to be updated to call winrt::get_module_lock().
- template<typename T, apartment_variable_leak_action leak_action = apartment_variable_leak_action::fail_fast,
- typename test_hook = wil::apartment_variable_platform>
- struct apartment_variable : details::apartment_variable_base<leak_action, test_hook>
- {
- using base = details::apartment_variable_base<leak_action, test_hook>;
- constexpr apartment_variable() = default;
- // Get current value or throw if no value has been set.
- T& get_existing() { return std::any_cast<T&>(base::get_existing()); }
- // Get current value or default-construct one on demand.
- T& get_or_create()
- {
- return std::any_cast<T&>(base::get_or_create(details::any_maker<T>()));
- }
- // Get current value or custom-construct one on demand.
- template<typename F>
- T& get_or_create(F&& f)
- {
- return std::any_cast<T&>(base::get_or_create(details::any_maker<T>(std::forward<F>(f))));
- }
- // get pointer to current value or nullptr if no value has been set
- T* get_if() { return std::any_cast<T>(base::get_if()); }
- // replace or create the current value, fail fasts if the value is not already stored
- template<typename V> void set(V&& value) { return base::set(std::forward<V>(value)); }
- // Clear the value in the current apartment.
- using base::clear;
- // Asynchronously clear the value in all apartments it is present in.
- using base::clear_all_apartments_async;
- // For testing only.
- // 1) To observe the state of the storage in the debugger assign this to
- // a temporary variable (const&) and watch its contents.
- // 2) Use this to test the implementation.
- using base::storage;
- // For testing only. The number of variables in the current apartment.
- using base::current_apartment_variable_count;
- };
- }
- #endif // __WIL_COM_APARTMENT_VARIABLE_INCLUDED
|