{"id":108183,"date":"2023-05-12T07:00:00","date_gmt":"2023-05-12T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108183"},"modified":"2023-05-12T06:57:29","modified_gmt":"2023-05-12T13:57:29","slug":"20230512-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230512-00\/?p=108183","title":{"rendered":"What are the duck-typing requirements of wil <CODE>com_ptr<\/CODE>?"},"content":{"rendered":"<p>We continue our survey of duck-typing requirements of various C++ COM smart pointer libraries by looking at wil&#8217;s <code>com_ptr<\/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;wil\/com.h&gt;<\/span>\r\n\u00a0\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">using TestPtr = wil::com_ptr&lt;Test&gt;;\u00a0\u00a0<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">using OtherPtr = wil::com_ptr&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.<span style=\"border: solid 1px currentcolor;\">detach<\/span>();\r\n    ptr.<span style=\"border: solid 1px currentcolor;\">attach<\/span>(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.put()<\/span>;\r\n    out = <span style=\"border: solid 1px currentcolor;\">ptr3.addressof()<\/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>Once again, we encounter the same glitch as we did with ATL <code>CComPtr<\/code> and WRL <code>ComPtr<\/code>:<\/p>\n<pre style=\"white-space: pre-wrap;\">com.h(363,1): error C2440: '=': cannot convert from 'void' to 'ULONG'\r\n<\/pre>\n<p>It&#8217;s coming from this code:<\/p>\n<pre>    void attach(pointer other) WI_NOEXCEPT\r\n    {\r\n        auto ptr = m_ptr;\r\n        m_ptr = other;\r\n        if (ptr)\r\n        {\r\n            ULONG ref;\r\n            ref = ptr-&gt;Release();\r\n            WI_ASSERT_MSG(((other != ptr) || (ref &gt; 0)), \"Bug: Attaching the same already assigned, destructed pointer\");\r\n        }\r\n    }\r\n<\/pre>\n<p>The code peeks at the reference count of the outgoing object and confirms that we didn&#8217;t attach a smart pointer to itself.<\/p>\n<p>As usual, the fix is to 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<\/pre>\n<p>We have to make a small tweak to the boilerplate by switching to lowercase names for <code>detach<\/code> and <code>attach<\/code>, because that&#8217;s how wil spells them.<\/p>\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 wil. You can use the <code>&amp;<\/code> operator, which is a shorthand for the method call <code>put()<\/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>addressof()<\/code>, which does not release the old pointer. Use <code>addressof()<\/code> in the cases where the parameter is used as an in\/out pointer.<\/p>\n<p>wil does not use the ATL trick of &#8220;coloring&#8221; the return value of the <code>-&gt;<\/code> operator, so you don&#8217;t have all the hassles of matching the signatures, but you also don&#8217;t get protection from accidentally doing a <code>ptr-&gt;Release()<\/code> when you meant to do a <code>ptr.reset()<\/code>. Fortunately, there is no <code>ptr.release()<\/code> method, so the mistake is a little less likely.<\/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>wil::com_ptr<\/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>wil::com_ptr<\/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>put()<\/code><\/td>\n<\/tr>\n<tr>\n<td>Preserve and receive pointer<\/td>\n<td><code>addressof()<\/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>Next time, we&#8217;ll finish our tour of COM smart pointer classes by looking at C++\/WinRT&#8217;s <code>com_ptr<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The experiments continue.<\/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-108183","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The experiments continue.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108183","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=108183"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108183\/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=108183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108183"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}