{"id":108173,"date":"2023-05-10T07:00:00","date_gmt":"2023-05-10T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108173"},"modified":"2023-05-10T08:48:55","modified_gmt":"2023-05-10T15:48:55","slug":"20230510-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230510-00\/?p=108173","title":{"rendered":"What are the duck-typing requirements of ATL <CODE>CComPtr<\/CODE>?"},"content":{"rendered":"<p>We continue our survey of duck-typing requirements of various C++ COM smart pointer libraries by looking at ATL&#8217;s <code>CComPtr<\/code>, running it through our standard tests.<\/p>\n<pre>\/\/ Dummy implementations of AddRef and Release for\r\n\/\/ testing purposes only. In real code, they would\r\n\/\/ manage the object reference count.\r\nstruct Test\r\n{\r\n    void AddRef() {}\r\n    void Release() {}\r\n    Test* AddressOf() { return this; }\r\n};\r\n\r\nstruct Other\r\n{\r\n    void AddRef() {}\r\n    void Release() {}\r\n};\r\n\r\n\/\/ Pull in the smart pointer library\r\n\/\/ (this changes based on library)\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">#include &lt;atlbase.h&gt;<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">#include &lt;atlcom.h&gt;\u00a0<\/span>\r\n\u00a0\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">using TestPtr = CComPtr&lt;Test&gt;;\u00a0\u00a0<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">using OtherPtr = CComPtr&lt;Other&gt;;<\/span>\r\n\r\nvoid test()\r\n{\r\n    Test test;\r\n\r\n    \/\/ Default construction\r\n    TestPtr ptr;\r\n\r\n    \/\/ Construction from raw pointer\r\n    TestPtr ptr2(&amp;test);\r\n\r\n    \/\/ Copy construction\r\n    TestPtr ptr3(ptr2);\r\n\r\n    \/\/ Attaching and detaching\r\n    auto p = ptr3.Detach();\r\n    ptr.Attach(p);\r\n\r\n    \/\/ Assignment from same-type raw pointer\r\n    ptr3 = &amp;test;\r\n\r\n    \/\/ Assignment from same-type smart pointer\r\n    ptr3 = ptr;\r\n\r\n    \/\/ Accessing the wrapped object\r\n    \/\/ (this changes based on library)\r\n    if (ptr.<span style=\"border: solid 1px currentcolor;\">p<\/span> != &amp;test) {\r\n        std::terminate(); \/\/ oops\r\n    }\r\n    if (ptr-&gt;AddressOf() != &amp;test) {\r\n        std::terminate(); \/\/ oops\r\n    }\r\n\r\n    \/\/ Returning to empty state\r\n    ptr3 = nullptr;\r\n\r\n    \/\/ Receiving a new pointer\r\n    \/\/ (this changes based on library)\r\n    Test** out = <span style=\"border: solid 1px currentcolor;\">&amp;ptr3<\/span>;\r\n    out = <span style=\"border: solid 1px currentcolor;\">&amp;ptr3.p<\/span>;\r\n\r\n    \/\/ Bonus: Comparison.\r\n    if (ptr == ptr2) {}\r\n    if (ptr != ptr2) {}\r\n    if (ptr &lt; ptr2) {}\r\n\r\n    \/\/ Litmus test: Accidentally bypassing the wrapper\r\n    ptr-&gt;AddRef();\r\n    ptr-&gt;Release();\r\n\r\n    \/\/ Litmus test: Construction from other-type raw pointer\r\n    Other other;\r\n    TestPtr ptr4(&amp;other);\r\n\r\n    \/\/ Litmus test: Construction from other-type smart pointer\r\n    OtherPtr optr;\r\n    TestPtr ptr5(optr);\r\n\r\n    \/\/ Litmus test: Assignment from other-type raw pointer\r\n    ptr = &amp;other;\r\n\r\n    \/\/ Litmus test: Assignment from other-type smart pointer\r\n    ptr = optr;\r\n\r\n    \/\/ Destruction\r\n}\r\n<\/pre>\n<p>A glitch in the core functionality tests happens when we call <code>ptr3.Attach(p)<\/code>:<\/p>\n<pre style=\"white-space: pre-wrap;\">atlcomcli.h(250,1): error C2440: 'initializing': cannot convert from 'void' to 'ULONG'\r\n<\/pre>\n<p>The problem is here:<\/p>\n<pre>    \/\/ Attach to an existing interface (does not AddRef)\r\n    void Attach(_In_opt_ T* p2) throw()\r\n    {\r\n        if (p)\r\n        {\r\n            ULONG ref = p-&gt;Release();\r\n            (ref);\r\n            \/\/ Attaching to the same object only works if duplicate\r\n            \/\/ references are being coalesced.  Otherwise\r\n            \/\/ re-attaching will cause the pointer to be released and\r\n            \/\/ may cause a crash on a subsequent dereference.\r\n            ATLASSERT(ref != 0 || p2 != p);\r\n        }\r\n        p = p2;\r\n    }\r\n<\/pre>\n<p>ATL expects the <code>Release<\/code> method to return a <code>ULONG<\/code> representing the new reference count. So let&#8217;s fix our class to do that.<\/p>\n<pre>struct Test\r\n{\r\n    void AddRef() { }\r\n    \/\/ Dummy implementation for testing purposes only.\r\n    ULONG Release() { return 1; }\r\n};\r\n<\/pre>\n<p>Okay, that gets us past the <code>Attach<\/code>\/<code>Detach<\/code> test.<\/p>\n<p>There are two ways to receive a new pointer. You can use the <code>&amp;<\/code> operator directly on the <code>CComPtr<\/code>, which asserts that the smart pointer is already empty. If the pointer is an in\/out parameter, then you can take the address of the public <code>p<\/code> member directly, which avoids the assertion check. There is no combined method for &#8220;release previous pointer before receiving a new one&#8221;.<\/p>\n<p>The comparison tests work as expected. They just compare the wrapped pointers.<\/p>\n<p>The accidental bypass litmus test and the test for accessing the wrapped object via the <code>-&gt;<\/code> operator are interesting because <code>CComPtr<\/code> uses a techique that author Jim Springfield called &#8220;coloring&#8221;:<\/p>\n<pre>template &lt;class T&gt;\r\nclass _NoAddRefReleaseOnCComPtr :\r\n    public T\r\n{\r\n    private:\r\n        STDMETHOD_(ULONG, AddRef)()=0;\r\n        STDMETHOD_(ULONG, Release)()=0;\r\n};\r\n\r\ntemplate &lt;class T&gt;\r\nclass CComPtrBase\r\n{\r\n    ...\r\n\r\n    _NoAddRefReleaseOnCComPtr&lt;T&gt;* operator-&gt;() const throw()\r\n    {\r\n        ATLASSERT(p!=NULL);\r\n        return (_NoAddRefReleaseOnCComPtr&lt;T&gt;*)p;\r\n    }\r\n\r\n    ...\r\n};\r\n<\/pre>\n<p>The trick here is that instead of returning the wrapped <code>T*<\/code> directly, we pretend that it is a pointer to the <code>T<\/code> portion of a <code>_NoAddRefReleaseOnCComPtr&lt;T&gt;<\/code>, and return a pointer to that derived class. The <code>_NoAddRefReleaseOnCComPtr&lt;T&gt;<\/code> class declares the <code>AddRef<\/code> and <code>Release<\/code> as private, thereby making them inaccessible from the resulting <code>-&gt;<\/code> operator:<\/p>\n<pre>ptr-&gt;Release(); \/\/ error: Cannot call private method\u00b9\r\n<\/pre>\n<p>In the case where <code>T<\/code> derives from <code>IUnknown<\/code>, these virtual <code>AddRef<\/code> and <code>Release<\/code> methods override the same-signature methods in <code>IUnknown<\/code>. But in the case where <code>T<\/code> does not derive from <code>IUnknown<\/code>, this adds a vtable to <code>_NoAddRefReleaseOnCComPtr<\/code>. Now, this vtable is never materialized, but it nevertheless introduces some pointer arithmetic that the compiler cannot immediately optimize away, because <a title=\"A static_cast is not always just a pointer adjustment\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20160224-00\/?p=93081\"> a <code>static_cast<\/code> is not always just a pointer adjustment<\/a>.<\/p>\n<pre>; ideally\r\nCComPtr&lt;Test&gt;::operator-&gt;()\r\n    lea     rax, [rcx-8]\r\n    ret\r\n\r\n; actually\r\nCComPtr&lt;Test&gt;::operator-&gt;()\r\n    lea     rdx, [rcx-8]\r\n    neg     rcx\r\n    sbb     rax, rax\r\n    and     rax, rdx\r\n    ret\r\n<\/pre>\n<p>The extra nonsense is there so that the cast from <code>(T*)<\/code> to <code>(_NoAddRefReleaseOnCComPtr&lt;T&gt;*)<\/code> produces <code>nullptr<\/code> when passed <code>nullptr<\/code>. (The &#8220;ideal&#8221; version would return <code>-8<\/code>.)<\/p>\n<p>I call this a missed optimization because when the compiler inlines the <code>-&gt;<\/code> operator, it can see that the resulting pointer is immediately dereferenced, so it cannot be null. Furthermore, the <code>+8<\/code> that comes afterward to convert the <code>(_NoAddRefReleaseOnCComPtr&lt;T&gt;*)<\/code> back to a <code>(T*)<\/code> exactly cancels out the <code>-8<\/code>, so all the pointer nonsense can be optimized out entirely. Bonusly furthermore, <code>this<\/code> can never be <code>nullptr<\/code>; invoking a method on a null pointer is undefined behavior.<\/p>\n<pre>    ; ptr2-&gt;AddressOf()\r\n    ; ideally\r\n\r\n    mov     rcx, [ptr2].p\r\n    call    Test::AddressOf\r\n\r\n    ; actually\r\n    mov     rcx, [ptr2].p\r\n    mov     eax, 8\r\n    test    rcx, rcx\r\n    cmove   rcx, rax\r\n    call    Test::AddressOf\r\n<\/pre>\n<p>All of these problems could have been avoided if <code>_NoAddRefReleaseOnCComPtr<\/code> had declared the private <code>AddRef<\/code> and <code>Release<\/code> as non-virtual.<\/p>\n<pre>template &lt;class T&gt;\r\nclass _NoAddRefReleaseOnCComPtr :\r\n    public T\r\n{\r\n    private:\r\n        ULONG AddRef();\r\n        ULONG Release();\r\n};\r\n<\/pre>\n<p>This still accomplishes the task of making the <code>AddRef<\/code> and <code>Release<\/code> methods inaccessible, but it doesn&#8217;t introduce a vtable, which means that the <code>static_cast<\/code> operations do not result in any code generation.<\/p>\n<p>But it&#8217;s too late to fix that now. That would be a binary breaking change.<\/p>\n<p>Other consequences of &#8220;coloring&#8221; are that the wrapped class <code>T<\/code> cannot be <code>final<\/code>, and if the <code>T::AddRef<\/code> and <code>T::Release<\/code> methods are virtual, they must return <code>ULONG<\/code> and use <code>STDMETHODCALLTYPE<\/code>.<\/p>\n<p>The <code>CComPtr<\/code> passes the other litmus tests: Most of the operations generate an error complaining that the types do not match. The interesting one is the last one, the assignment of an other-type smart pointer.<\/p>\n<pre>    template &lt;typename Q&gt;\r\n    T* operator=(_Inout_ const CComPtr&lt;Q&gt;&amp; lp) throw()\r\n    {\r\n        if(!this-&gt;IsEqualObject(lp) )\r\n        {\r\n            AtlComQIPtrAssign2((IUnknown**)&amp;this-&gt;p, lp, __uuidof(T));\r\n        }\r\n        return *this;\r\n    }\r\n<\/pre>\n<p>This one fails because the code uses <code>CComPtrBase::IsEqualObject<\/code>, which in turn does not compile due to lack of <code>Query\u00adInterface<\/code> support. Which is a good thing, because there is a C-style cast to <code>IUnknown**<\/code> in the call to <code>AtlComQIPtrAssign2<\/code>, which requires that our underlying type <code>T<\/code> derive from <code>IUnknown<\/code>.<\/p>\n<p>Okay, so here&#8217;s the scorecard for <code>CComPtr<\/code>.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th colspan=\"2\"><code>CComPtr<\/code> scorecard<\/th>\n<\/tr>\n<tr>\n<td>Default construction<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Construct from raw pointer<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Copy construction<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Destruction<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Attach and detach<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Assign to same-type raw pointer<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Assign to same-type smart pointer<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Fetch the wrapped pointer<\/td>\n<td><code>p<\/code>, or implicit conversion<\/td>\n<\/tr>\n<tr>\n<td>Access the wrapped object<\/td>\n<td><code>-&gt;<\/code> (suboptimal)<\/td>\n<\/tr>\n<tr>\n<td>Receive pointer via <code>&amp;<\/code><\/td>\n<td>must be empty<\/td>\n<\/tr>\n<tr>\n<td>Release and receive pointer<\/td>\n<td><code>&amp;<\/code><\/td>\n<\/tr>\n<tr>\n<td>Preserve and receive pointer<\/td>\n<td><code>&amp;p<\/code><\/td>\n<\/tr>\n<tr>\n<td>Return to empty state<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Comparison<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Accidental bypass<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Construct from other-type raw pointer<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Construct from other-type smart pointer<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Assign from other-type raw pointer<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td>Assign from other-type smart pointer<\/td>\n<td>Pass<\/td>\n<\/tr>\n<tr>\n<td colspan=\"2\">Notes:<br \/>\n<code>T<\/code> may not be <code>final<\/code>.<br \/>\n<code>T<\/code> must have a method of the form <code>ULONG Release()<\/code>.<br \/>\nThe <code>T::Release<\/code> method must return nonzero if the object is still alive.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Netx time, we&#8217;ll look at WRL&#8217;s <code>ComPtr<\/code>.<\/p>\n<p>\u00b9 Though you can hack around this by forcing a call to the base class version, or just using the raw wrapped pointer.<\/p>\n<pre>\/\/ sneaky! ducking under the ribbon!\r\nptr-&gt;Test::Release();\r\nptr.p-&gt;Release();\r\n<\/pre>\n<p>The purpose of the &#8220;coloring&#8221; is not be be bulletproof. It&#8217;s just to prevent you from calling <code>AddRef<\/code> and <code>Release<\/code> by mistake.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Looking for the minimum requirements.<\/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-108173","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Looking for the minimum requirements.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108173","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=108173"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108173\/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=108173"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108173"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108173"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}