{"id":108161,"date":"2023-05-09T07:00:00","date_gmt":"2023-05-09T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108161"},"modified":"2023-10-05T17:42:15","modified_gmt":"2023-10-06T00:42:15","slug":"20230509-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230509-00\/?p=108161","title":{"rendered":"What are the duck-typing requirements of MFC <CODE>IPTR<\/CODE>?"},"content":{"rendered":"<p>We continue our survey of duck-typing requirements of various C++ COM smart pointer libraries by looking at MFC&#8217;s <code>IPTR<\/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;afxole.h&gt;<\/span>\r\n\u00a0\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">extern const GUID IID_Test;  <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">extern const GUID IID_Other; <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">using TestPtr = IPTR(Test);  <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">using OtherPtr = IPTR(Other);<\/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    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">auto p = ptr3.GetInterfacePtr();<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">ptr3.Detach();\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/span>\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;\">GetInterfacePtr()<\/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    <span style=\"border: solid 1px currentcolor;\">ptr3.Release(); \/\/ requires ptr3 be non-empty<\/span>\r\n    ptr3 = <span style=\"border: solid 1px currentcolor;\">static_cast&lt;Test*&gt;<\/span>(nullptr);\r\n    <span style=\"border: solid 1px currentcolor;\">ptr3.Attach(nullptr);<\/span>\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\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>The <code>IPTR<\/code> macro assumes that the <code>IID<\/code> that corresponds to the alleged interface is named <code>IID_Blah<\/code>. We give it a dummy ID interface in the form of a reference to a variable that is never defined. This ensures a linker error if the code ever tries to use that fake interface ID.<\/p>\n<p>The <code>IPTR.Detach<\/code> method does not return the detached pointer. You have to fetch it yourself via <code>Get\u00adInterface\u00adPtr()<\/code> if you want to save it.<\/p>\n<p>Returning the smart pointer to an empty state can be done in multiple ways.<\/p>\n<ul>\n<li>The <code>Release()<\/code> method releases the wrapped pointer, but there must be a non-null wrapped pointer in the first place. It is an error to call <code>ptr3.Release()<\/code> on an empty <code>ptr3<\/code>.<\/li>\n<li>You can use the assignment operator to assign a <code>nullptr<\/code>. However, there are multiple assignment operators,\u00b9 so you have to cast the <code>nullptr<\/code> to <code>Test*<\/code> to say, &#8220;I am assigning a new pointer to be wrapped.&#8221;<\/li>\n<li>A sneaky way to avoid the extra typing is to use the <code>Attach()<\/code> method to attach a null pointer. The first parameter to <code>Attach()<\/code> is always a <code>Test*<\/code>, so there is no ambiguity in passing just <code>nullptr<\/code>.<\/li>\n<\/ul>\n<p>The only way to receive a new pointer is to use the <code>&amp;<\/code> operator. This operator releases any previous wrapped pointer before receiving the new one.<\/p>\n<p>The <code>IPTR<\/code> fails the bonus comparison test, but not through any fault of the <code>Test<\/code> class. The <code>IPTR<\/code> simply doesn&#8217;t support comparison at all, not even for COM interfaces.<\/p>\n<p>The <code>IPTR<\/code> macro fails the &#8220;accidental bypass&#8221; litmus test, in the same way that <code>_com_ptr_t<\/code> did. As with <code>_com_ptr_t<\/code>, there are two ways of releasing the object that are dangerously similar:<\/p>\n<pre>ptr2.Release(); \/\/ good\r\nptr2-&gt;Release(); \/\/ bad\r\n<\/pre>\n<p>The same cautions apply.<\/p>\n<p>The <code>IPTR<\/code> passes the other litmus tests: All of the attempts to convert or assign from another type of smart pointer or raw pointer generate errors.<\/p>\n<p>Okay, so here&#8217;s the scorecard for <code>IPTR<\/code>.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th colspan=\"2\">MFC <code>IPTR<\/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>GetInterfacePtr()<\/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>&amp;<\/code><\/td>\n<\/tr>\n<tr>\n<td>Preserve and receive pointer<\/td>\n<td>N\/A<\/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>Not supported<\/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<\/tbody>\n<\/table>\n<p>Next time, we&#8217;ll look at ATL&#8217;s <code>CComPtr<\/code>.<\/p>\n<p>\u00b9 The other assignment operators accept <code>CLSID<\/code> and <code>PCWSTR<\/code> and create a new object identifed by the <code>CLSID<\/code> or ProgId. Fortunately, it&#8217;s unlikely that you will assign one of these things to a a COM smart pointer by mistake.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Seeing what goes wrong and trying to fix it.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-108161","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Seeing what goes wrong and trying to fix it.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108161","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=108161"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108161\/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=108161"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108161"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108161"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}