{"id":104865,"date":"2021-02-15T07:00:00","date_gmt":"2021-02-15T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104865"},"modified":"2021-02-14T19:29:23","modified_gmt":"2021-02-15T03:29:23","slug":"20210215-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210215-00\/?p=104865","title":{"rendered":"The COM static store, part 6: Using C++ weak references"},"content":{"rendered":"<p>Last time, we looked at <a title=\"The\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20210212-00\/?p=104847\"> using a COM weak reference to access our singleton quickly<\/a>. so you can access them all at once. That improves the efficiency of accessing objects in the COM static store, but you can do even better.<\/p>\n<p>Use a C++ weak reference.<\/p>\n<p>The idea here is that you create a <code>shared_ptr<\/code> to your state and put that <code>shared_ptr<\/code> inside an <code>IInspectable<\/code>, and put that <code>IInspectable<\/code> in the COM static store. The lifetime of the data is therefore controlled by the COM static store, and it will be destructed when COM shuts down. Meanwhile, you keep a <code>weak_ptr<\/code> to the state in an easily-accessed global variable. When you want to access the data, you just <code>lock()<\/code> the weak pointer. If the lock fails, then it means that the shared data doesn&#8217;t exist (either it was never created, or it was destroyed when COM was shut down), so you get to make a new one.<\/p>\n<p><b>Exercise<\/b>: What&#8217;s the scenario where we need to recreate the data after COM was shut down?<\/p>\n<pre>\/\/ C++\/WinRT\r\nstruct SharedState\r\n{\r\n    int some_value = 0;\r\n    winrt::com_ptr&lt;IStream&gt; stream;\r\n    std::vector&lt;winrt::com_ptr&lt;IStorage&gt;&gt; storages;\r\n};\r\n\r\nstruct SharedStateHolder :\r\n    winrt::implements&lt;SharedStateHolder,\r\n                      winrt::Windows::Foundation::IInspectable&gt;\r\n{\r\n    std::shared_ptr&lt;SharedState&gt; shared =\r\n       std::make_shared&lt;SharedState&gt;();\r\n};\r\n\r\nstd::shared_ptr&lt;SharedState&gt;\r\nGetSingletonSharedState()\r\n{\r\n    static std::weak_ptr&lt;SharedState&gt; weak;\r\n    static winrt::slim_mutex lock;\r\n    {\r\n        std::shared_lock guard{ lock };\r\n        if (auto shared = weak.lock()) return shared;\r\n    }\r\n\r\n    auto value = winrt::make_self&lt;SharedStateHolder&gt;();\r\n\r\n    winrt::slim_lock_guard guard{ lock };\r\n    if (auto shared = weak.lock()) return shared;\r\n\r\n    CoreApplication::Properties().Insert(L\"SharedState\", *value);\r\n    weak = value-&gt;shared;\r\n    return value-&gt;shared;\r\n}\r\n<\/pre>\n<p>The idea here is that our shared state is stored in a <code>SharedState<\/code> structure, which is kept alive by a <code>shared_ptr<\/code> in the <code>SharedStateHolder<\/code>, which is in turn kept alive by the COM static store. Therefore, it is the COM static store that ultimately controls the lifetime of the shared state.<\/p>\n<p>When we create the shared state, we also keep a weak reference to it in a static variable. That weak reference gives us quick access to the shared state, much faster than getting the <code>CoreApplication<\/code>&#8216;s <code>Properties<\/code> and then hunting around inside it. Resolving a C++ weak reference is just chasing a few pointers and doing an atomic increment.<\/p>\n<p><b>Bonus reading<\/b>: <a href=\"https:\/\/channel9.msdn.com\/Series\/C9-Lectures-Stephan-T-Lavavej-Advanced-STL\/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-1-of-n\"> Advanced STL, part 1: <code>shared_ptr<\/code><\/a> by <a href=\"https:\/\/twitter.com\/StephanTLavavej\"> Stephan T. Lavavej<\/a>.<\/p>\n<p>If the weak pointer fails to resolve, then we need to go make a new shared state object. After entering the exclusive lock, we check again, in case somebody beat us to it.<\/p>\n<p>Assuming we didn&#8217;t hit the race condition, we put the object into the COM static store and update our weak pointer. (The order here is important in case the insertion operation fails.)<\/p>\n<p>When COM shuts down, it will release all the objects in the COM static store, which will ultimately lead to the destruction of the shared state. The weak pointer, however, continues to point to an expired control block. When the weak pointer is destructed, even that control block gets freed.<\/p>\n<p>The trick here is that the C++ weak pointer is not a COM object and therefore you don&#8217;t run into the problem of using a COM object after COM has shut down. It&#8217;s a weak pointer to an object in this same module, so there is no external code involved. (Indeed, there are no virtual methods at all! That&#8217;s part of what makes C++ weak pointers so fast.)<\/p>\n<p>We can now generalize this pattern into a helper:<\/p>\n<pre>template&lt;typename D, typename Type&gt;\r\nstruct ComSingleton\r\n{\r\n    std::weak_ptr&lt;Type&gt; weak;\r\n    winrt::slim_mutex lock;\r\n\r\n    std::shared_ptr&lt;Type&gt; Get()\r\n    {\r\n        {\r\n            std::shared_lock guard{ lock };\r\n            if (auto shared = weak.lock()) return shared;\r\n        }\r\n\r\n        struct Holder : winrt::implements&lt;\r\n            Holder, winrt::Windows::Foundation::IInspectable&gt;\r\n        {\r\n            std::shared_ptr&lt;Type&gt; shared =\r\n                std::make_shared&lt;Type&gt;();\r\n        };\r\n        auto value = winrt::make_self&lt;Holder&gt;();\r\n\r\n        winrt::slim_lock_guard guard{ lock };\r\n        if (auto shared = weak.lock()) return shared;\r\n\r\n        winrt::Windows::ApplicationModel::Core::\r\n        CoreApplication::Properties().Insert(\r\n            static_cast&lt;D*&gt;(this)-&gt;name(), *value);\r\n        weak = value-&gt;shared;\r\n        return value-&gt;shared;\r\n    }\r\n\r\n    void Reset() try\r\n    {\r\n        winrt::Windows::ApplicationModel::Core::\r\n        CoreApplication::Properties().Remove(\r\n            static_cast&lt;D*&gt;(this)-&gt;name());\r\n    }\r\n    catch (winrt::hresult_out_of_bounds const&amp;) {}\r\n};\r\n<\/pre>\n<p>For each singleton thing you want to create, you create a corresponding <code>ComSingleton<\/code> specialization, providing a type that provides the name under which the item should be recorded.<\/p>\n<pre>struct SharedState\r\n{\r\n    int some_value = 0;\r\n    winrt::com_ptr&lt;IStream&gt; stream;\r\n    std::vector&lt;winrt::com_ptr&lt;IStorage&gt;&gt; storages;\r\n};\r\n\r\nstruct SharedStateSingleton :\r\n    ComSingleton&lt;SharedStateSingleton, SharedState&gt;\r\n{\r\n    static constexpr decltype(auto) name()\r\n        { return L\"SharedState\"; };\r\n};\r\n\r\nSharedStateSingleton singleton;\r\n\r\nvoid Something()\r\n{\r\n    auto state = singleton.Get();\r\n}\r\n<\/pre>\n<p>The name of the key in the COM static store is provided in the form of a function because that lets you generate multiple instances of the <code>SharedState<\/code> under keys generated at runtime. And also because string literals aren&#8217;t valid non-type template parameters (until C++20).<\/p>\n<p>For completeness, I also added a <code>Reset<\/code> method which destroys the singleton.<\/p>\n<p>One thing you may have noticed is that this version creates an extra allocation, since we have both a <code>shared_ptr<\/code> and a <code>IInspectable<\/code>. However, notice that the <code>IInspectable<\/code> exists solely to manage the lifetime of the shared object. Nobody actually accesses the object via the <code>IInspectable<\/code>. In fact, once the <code>IInspectable<\/code> has been put into the COM static store, it is completely forgotten! The memory and code for the <code>IInspectable<\/code> will not be used again until the static store is torn down.<\/p>\n<p>And since the entire purpose is to manage a singleton object, it&#8217;s not like you&#8217;re going to be allocating a lot of these little babysitter <code>IInspectable<\/code> objects. There&#8217;s going to be only one per singleton. The extra memory cost is not likely to be significant, and it leads to the removal of virtual calls from the common code path where you resolve the C++ reference to a live C++ object.<\/p>\n<p>And then if you think about it some more, you realize that the C++ weak reference didn&#8217;t add any allocations at all, because the version with COM weak references also allocated two objects anyway: One for the <code>IInspectable<\/code> and one for the COM weak reference. All you did was trade one type of weak reference for another. (Assuming you use <code>make_<wbr \/>shared<\/code> to put the control block and payload in the same allocation.)<\/p>\n<p>That completes our whirlwind tour of the COM static store. It&#8217;s a great place to keep your stuff, if your stuff needs to vanish at the same time COM does.<\/p>\n<p><b>Answer to exercise<\/b>: The scenario where we need to recreate the data after COM was shut down is when the application shuts down COM, and then starts it back up again. In that case, the COM shutdown will destroy all of the shared state. When COM starts back up, the app can call back into your COM object, and it will then need to create a new set of data.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A quicker way to get to what you have.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-104865","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A quicker way to get to what you have.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104865","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=104865"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104865\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=104865"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104865"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104865"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}