{"id":108177,"date":"2023-05-11T07:00:00","date_gmt":"2023-05-11T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108177"},"modified":"2023-05-11T06:28:33","modified_gmt":"2023-05-11T13:28:33","slug":"20230511-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230511-00\/?p=108177","title":{"rendered":"What are the duck-typing requirements of WRL <CODE>ComPtr<\/CODE>?"},"content":{"rendered":"<p>We continue our survey of duck-typing requirements of various C++ COM smart pointer libraries by looking at WRL&#8217;s <code>ComPtr<\/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;\">#include &lt;wrl\/client.h&gt;<\/span>\r\n\u00a0\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">using TestPtr = Microsoft::WRL::ComPtr&lt;Test&gt;;\u00a0\u00a0<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">using OtherPtr = Microsoft::WRL::ComPtr&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;\">Get()<\/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;\">ptr3.ReleaseAndGetAddressOf()<\/span>;\r\n    out = <span style=\"border: solid 1px currentcolor;\">ptr3.GetAddressOf()<\/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>We encounter the same glitch as we did with ATL <code>CComPtr<\/code>, but this time it happens twice. First, it happens at construction of the WRL <code>ComPtr<\/code>:<\/p>\n<pre style=\"white-space: pre-wrap;\">client.h(235,1): error C2440: '=': cannot convert from 'void' to 'unsigned long'\r\n<\/pre>\n<p>Due to this code:<\/p>\n<pre>    unsigned long InternalRelease() throw()\r\n    {\r\n        unsigned long ref = 0;\r\n        T* temp = ptr_;\r\n\r\n        if (temp != nullptr)\r\n        {\r\n            ptr_ = nullptr;\r\n            ref = temp-&gt;Release();\r\n        }\r\n\r\n        return ref;\r\n    }\r\n<\/pre>\n<p>WRL wants to propagate the return value of <code>Release<\/code>, and it expects the method to return an <code>unsigned long<\/code>.<\/p>\n<p>The other failure occurs in a familiar place: On the <code>Attach<\/code>.<\/p>\n<pre style=\"white-space: pre-wrap;\">client.h(22,1): error C3313: 'ref': variable cannot have the type 'void'\r\n<\/pre>\n<p>The problem is here:<\/p>\n<pre>    void Attach(_In_opt_ InterfaceType* other) throw()\r\n    {\r\n        if (ptr_ != nullptr)\r\n        {\r\n            auto ref = ptr_-&gt;Release();\r\n            (void)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            __WRL_ASSERT__(ref != 0 || ptr_ != other);\r\n        }\r\n\r\n        ptr_ = other;\r\n    }\r\n<\/pre>\n<p>It&#8217;s clear that the WRL code derived from the ATL code, seeing as this is pretty much identical to the ATL code, down to the comments.<\/p>\n<p>So we have to fix our class in the same way we fixed it for ATL: Make the <code>Release<\/code> method return a <code>ULONG<\/code> representing the new reference count.<\/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\r\nstruct Other\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>Once we fix that up, the basic tests all pass. The comparison tests compare the wrapped pointers.<\/p>\n<p>There are three ways to receive a pointer in WRL. You can use the <code>&amp;<\/code> operator, which is a shorthand for the method call <code>ReleaseAndGetAddressOf()<\/code>, which releases the old pointer and nulls it out, then returns the address of the pointer so a new value can be placed there. Alternatively, you can use <code>GetAddressOf()<\/code>, which does not release the old pointer. Use <code>GetAddressOf()<\/code> in the cases where the parameter is used as an in\/out pointer.<\/p>\n<p>WRL does not use the ATL trick of &#8220;coloring&#8221; the return value of the <code>-&gt;<\/code> operator, so it does not have the ATL requirements that the wrapped class <code>T<\/code> be non-final, or that the <code>T::AddRef<\/code> and <code>T::Release<\/code> methods must have the same signature and calling convention as <code>IUnknown<\/code> if they are virtual.<\/p>\n<p>On the other hand, the lack of &#8220;coloring&#8221; means that you can accidentally write<\/p>\n<pre>ptr2-&gt;Release();\r\n<\/pre>\n<p>instead of <code>ptr2.Reset()<\/code>. WRL tries to make the problem less likely to occur by using a different name (<code>Reset<\/code>) to try to reduce the chance of confusion.<\/p>\n<p>The other-type litmus tests all pass. They all result in various types of compile-time errors.<\/p>\n<p>Okay, so here&#8217;s the scorecard for <code>ComPtr<\/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>ComPtr<\/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>Get()<\/code><\/td>\n<\/tr>\n<tr>\n<td>Access the wrapped object<\/td>\n<td><code>-&gt;<\/code><\/td>\n<\/tr>\n<tr>\n<td>Receive pointer via <code>&amp;<\/code><\/td>\n<td>release old<\/td>\n<\/tr>\n<tr>\n<td>Release and receive pointer<\/td>\n<td><code>ReleaseAndGetAddressOf()<\/code><\/td>\n<\/tr>\n<tr>\n<td>Preserve and receive pointer<\/td>\n<td><code>GetAddressOf()<\/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>Fail<\/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> 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>Our next smart pointer library will be <code>com_ptr<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Another round of experimentation.<\/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-108177","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Another round of experimentation.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108177","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=108177"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108177\/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=108177"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108177"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108177"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}