{"id":109317,"date":"2024-01-25T07:00:00","date_gmt":"2024-01-25T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109317"},"modified":"2024-01-24T21:33:54","modified_gmt":"2024-01-25T05:33:54","slug":"20240125-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240125-00\/?p=109317","title":{"rendered":"How can I give away a COM reference just before my object destructs?"},"content":{"rendered":"<p>Last time, we noted that <a title=\"The dangerous implementations of the IMemoryBufferReference.Closed event\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240124-00\/?p=109311\"> most implementations of the <code>IMemory\u00adBuffer\u00adReference.Closed<\/code> event are broken<\/a> because they <a title=\"Avoiding double-destruction when an object is released\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20050928-10\/?p=34013\"> hand out COM references to themselves after C++ has already committed to destructing the object<\/a>, so calling <code>Add\u00adRef()<\/code> on the COM reference doesn&#8217;t actually extend the object&#8217;s lifetime.<\/p>\n<p>So how do you raise this <code>Closed<\/code> event when the last reference is released, if releasing the last reference also commits you to destruction?<\/p>\n<p>More generally, how can I execute some code when the last reference is released, if that code might try to extend the object&#8217;s lifetime?<\/p>\n<p>One solution is to keep track of two different reference counts, one for &#8220;client-visible reference counts&#8221; and one for &#8220;internal reference counts&#8221;, and then raise the <code>Closed<\/code> event when the client-visible reference count goes to zero but before the internal reference count also goes to zero. (You would destruct the object when <i>both<\/i> reference counts go to zero.)<\/p>\n<p>But I&#8217;m going to do things a little differently.<\/p>\n<p>My trick is to have the object call <code>AddRef()<\/code> on itself at construction, so that the object reference count is 2 when it is given to the caller. Since one of those reference counts is artificial, we relize that when the reference count drops to 1, that means that all &#8220;client-visible&#8221; references have been released. The only thing keeping the object alive is the artificial reference.<\/p>\n<p>We can now perform that cleanup operation, passing a COM reference to a proper object which is not yet committed to destruction. After the call returns, we release the artificial reference. If the cleanup operation did not attempt to extend the lifetime of the COM reference, then then our release of the artificial reference will drop the reference count to zero, and the object destructs immediately. But if it used <code>AddRef()<\/code> to extend the lifetime, then our release will not drop the reference count to zero, and the object will remain alive. It&#8217;s being cleaned up, but still alive.<\/p>\n<p>Of course, we have to be careful that when the reference count drops to 1 a <i>second<\/i> time, we don&#8217;t get confused and try to clean up a second time and (worse) release the nonexistent artifical reference.<\/p>\n<p>Here&#8217;s how you could implement this pattern in various COM frameworks.<\/p>\n<p>We start with WRL, which has no real surprises.<\/p>\n<pre>\/\/ C++\/WRL\r\n\r\nstruct ObjectWithSpecialCleanup :\r\n    RuntimeClass&lt;IWhatever&gt;\r\n{\r\n    ObjectWithSpecialCleanup()\r\n    {\r\n        \/\/ WRL does not support aggregation,\r\n        \/\/ so don't need to assert \"not aggregating\"\r\n        InternalAddRef();\r\n    }\r\n\r\n    STDMETHOD_(ULONG, Release)() override\r\n    {\r\n        auto count = RuntimeClass::Release();\r\n\r\n        if (count == 1) {\r\n            if (!m_cleanupStarted.exchange(true, std::memory_order_relaxed)) {\r\n                DoCleanup(this); \/* Might AddRef this object *\/\r\n                count = RuntimeClass::Release();\r\n            }\r\n        }\r\n\r\n        return count;\r\n    }\r\n\r\n    \u27e6 ... \u27e7\r\n\r\n    std::atomic&lt;bool&gt; m_cleanupStarted;\r\n};\r\n<\/pre>\n<p>Next comes C++\/WinRT:<\/p>\n<pre>\/\/ C++\/WinRT\r\n\r\nstruct ObjectWithSpecialCleanup :\r\n    implements&lt;ObjectWithSpecialCleanup,\r\n               IWhatever&gt;\r\n{\r\n    static_assert(<a title=\"How do I prevent my C++\/WinRT implementation class from participating in COM aggregation?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240108-00\/?p=109250\">!outer()<\/a>, \"Must not be composable\");\r\n\r\n    ObjectWithSpecialCleanup()\r\n    {\r\n        NonDelegatingAddRef();\r\n    }\r\n\r\n    decltype(std::declval&lt;implements&gt;().Release())\r\n    __stdcall Release() noexcept override\r\n    {\r\n        auto count = NonDelegatingRelease();\r\n\r\n        if (count == 1) {\r\n            if (!m_cleanupStarted.exchange(true, std::memory_order_relaxed)) {\r\n                DoCleanup(this); \/* Might AddRef this object *\/\r\n                count = NonDelegatingRelease();\r\n            }\r\n        }\r\n\r\n        return count;\r\n    }\r\n\r\n    \u27e6 ... \u27e7\r\n\r\n    std::atomic&lt;bool&gt; m_cleanupStarted;\r\n};\r\n<\/pre>\n<p>In C++\/WinRT, we use the trick we learned a little while ago to <a title=\"How do I prevent my C++\/WinRT implementation class from participating in COM aggregation?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240108-00\/?p=109250\"> ensure that the C++\/WinRT implementation class is not aggregated<\/a>.<\/p>\n<p>The biggest pain point is figuring out the correct return value for the overridden <code>Release<\/code> method. The return type depends on whether classic COM interop has been enabled in C++\/WinRT. If so, then the return value of <code>Release()<\/code> needs to be <code>ULONG<\/code>, to match the classic COM <code>Release()<\/code> method. But if not, then the return value needs to be <code>uint32_t<\/code>, to match the C++\/WinRT version. We solve the problem by simply looking at the method we are trying to override and saying that we return whatever that guy returns.<\/p>\n<p>Next up is manual implementation.<\/p>\n<pre>\/\/ Manually implemented\r\nstruct MyPage : IPage\r\n{\r\n    MyPage()\r\n        \/\/ Bonus refcount is released when count drops to 1\r\n        : m_refCount(2) { }\r\n\r\n    STDMETHOD_(ULONG, Release)() override\r\n    {\r\n        auto count = InterlockedDecrement(&amp;m_refCount);\r\n\r\n        if (count == 1) {\r\n            if (!m_cleanupStarted.exchange(true, std::memory_order_relaxed)) {\r\n                DoCleanup(this); \/* Might AddRef this object *\/\r\n                count = InterlockedDecrement(&amp;m_refCount);\r\n            }\r\n        }\r\n\r\n        return count;\r\n    }\r\n\r\n    LONG m_refCount;\r\n    std::atomic&lt;bool&gt; m_cleanupStarted;\r\n    \u27e6 ... \u27e7\r\n};\r\n<\/pre>\n<p>The sneaky part here is that instead of incrementing the reference count in the body of the constructor, we just combine the extra reference count into the initial value.<\/p>\n<p>The final example is ATL.<\/p>\n<pre>\/\/ ATL\r\nclass ObjectWithSpecialCleanup :\r\n    public CComObjectRootEx&lt;CComMultiThreadModel&gt;,\r\n    public CComCoClass&lt;ObjectWithSpecialCleanup&gt;,\r\n    public IWhatever\r\n{\r\npublic:\r\n    \/\/ <a title=\"How do I prevent my ATL class from participating in COM aggregation? DECLARE_NOT_AGGREGATABLE didn't work\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240101-00\/?p=109214\">Prevent this class from being aggregated<\/a>\r\n    static constexpr void* m_pOuterUnknown = nullptr;\r\n\r\n    BEGIN_COM_MAP(Widget)\r\n        COM_INTERFACE_ENTRY(IAgileObject)\r\n    END_COM_MAP()\r\n\r\n    \/\/ NOTE! <a title=\"Implementing two-phase initialization with ATL\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240118-00\/?p=109286\">Requires creation via two-phase initialization<\/a>\r\n    HRESULT InitializeComponent() noexcept\r\n    {\r\n        InternalAddRef();\r\n        m_cleanupNeeded.store(true, std::memory_order_relaxed);\r\n        return S_OK;\r\n    }\r\n\r\n    ULONG InternalRelease()\r\n    {\r\n        auto count = CComObjectRootEx::InternalRelease();\r\n\r\n        if (count == 1) {\r\n            if (m_cleanupNeeded.exchange(false, std::memory_order_relaxed)) {\r\n                DoCleanup(this); \/* Might AddRef this object *\/\r\n                count = CComObjectRootEx::InternalRelease();\r\n            }\r\n        }\r\n\r\n        return count;\r\n    }\r\n\r\n    std::atomic&lt;bool&gt; m_cleanupNeeded;\r\n    \u27e6 ... \u27e7\r\n};\r\n\r\n<\/pre>\n<p>ATL is the outlier.<\/p>\n<p>For ATL, we need to use two-phase initialization (<a title=\"Implementing two-phase initialization with ATL\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240118-00\/?p=109286\">which we learned how to do a little while ago<\/a>) because we are not allowed to fiddle with the reference count in the constructor or in the optional <code>FinalConstruct()<\/code> method. We mark the object as needing cleanup only if we make it to the second phase. The sense of the flag is reversed for ATL because it starts out not needing cleanup, and gains the cleanup requirement later. The other libraries don&#8217;t have this &#8220;nascent state&#8221; where the object is constructed but not yet ready.\u00b9<\/p>\n<p>The memory order for the atomic accesses to the cleanup flag is relaxed because we are counting on the object to protect its own internal state from multithreaded access when necessary.<\/p>\n<p>\u00b9 ATL uses the policy that objects are created with a reference count of zero, rather than one. This may have made sense at the time, but it creates complications when you write code that runs during this dangerous &#8220;zero-refcount&#8221; period.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You have to do it before committing to destruction.<\/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-109317","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You have to do it before committing to destruction.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109317","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=109317"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109317\/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=109317"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109317"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109317"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}