{"id":108156,"date":"2023-05-08T07:00:00","date_gmt":"2023-05-08T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108156"},"modified":"2023-05-08T06:59:15","modified_gmt":"2023-05-08T13:59:15","slug":"20230508-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230508-00\/?p=108156","title":{"rendered":"What are the duck-typing requirements of <CODE>_com_ptr_t<\/CODE>?"},"content":{"rendered":"<p>The great thing about COM smart pointer classes is that <a href=\"https:\/\/en.wikiquote.org\/wiki\/Grace_Hopper\"> there are so many to choose from<\/a>.<\/p>\n<p>In general, the smart pointer classes don&#8217;t even care that the thing you&#8217;re wrapping isn&#8217;t even a COM interface pointer. As long as your class supports appropriate method signatures, the smart pointer classes will take them. This is a general property of C++ templates: If the template type parameter supports the operations performed by the template, then it doesn&#8217;t really matter where they came from. The case where you can run into trouble is when the library does a blind cast of your pointer to <code>IUnknown*<\/code> (either via <code>reinterpret_cast<\/code> or more commonly by a C-style cast), in which case the code is assuming that your class really does derive from <code>IUnknown<\/code>.\u00b9<\/p>\n<p>So let&#8217;s look at the minimum requirements for simply using the COM smart pointer wrapper as a way to manage object lifetime, without any actual COM functionality like changing interfaces via <code>QueryInterface<\/code>. Here are the operations we&#8217;re looking for:<\/p>\n<ul>\n<li>Default construction,<\/li>\n<li>Construction from a raw pointer (<code>AddRef<\/code>),<\/li>\n<li>Copy construction (<code>AddRef<\/code>),<\/li>\n<li>Destruction (<code>Release<\/code>),<\/li>\n<li>Attaching and detaching (adopting and relinquishing ownership),<\/li>\n<li>Assignment to same-type raw pointer (<code>AddRef<\/code>),<\/li>\n<li>Assignment to same-type smart pointer (<code>AddRef<\/code>),<\/li>\n<li>Accessing the wrapped object,<\/li>\n<li>Returning to empty state (<code>Release<\/code>),<\/li>\n<li>Receiving a new pointer,<\/li>\n<li>Bonus: Comparison.<\/li>\n<li>Litmus test: Accidentally bypassing the wrapper.<\/li>\n<li>Litmus test: Construction from other-type raw pointer.<\/li>\n<li>Litmus test: Construction from other-type smart pointer.<\/li>\n<li>Litmus test: Assignment from other-type raw pointer.<\/li>\n<li>Litmus test: Assignment from other-type smart pointer.<\/li>\n<\/ul>\n<p>In addition to some core operations, we have a bonus operation, as well as some litmus tests to see what happens if you make a mistake.<\/p>\n<p>We&#8217;ll start with the <code>_com_ptr_t<\/code> class that comes with the <code>#import<\/code> directive. All of our tests will use the same basic framework. For the most part, our changes will be focused on the highlighted portions.<\/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=\"color: #08f;\">#include &lt;comdef.h&gt;<\/span>\r\n<span style=\"color: #08f;\">extern IID IID_NeverDefined; \/\/ never defined<\/span>\r\n<span style=\"color: #08f;\">_COM_SMARTPTR_TYPEDEF(Test, IID_NeverDefined);<\/span>\r\n<span style=\"color: #08f;\">_COM_SMARTPTR_TYPEDEF(Other, IID_NeverDefined);<\/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=\"color: #08f;\">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    ptr3 = nullptr;\r\n\r\n    \/\/ Receiving a new pointer\r\n    \/\/ (this changes based on library)\r\n    Test** out = <span style=\"color: #08f;\">&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>_COM_<wbr \/>SMARTPTR_<wbr \/>TYPEDEF<\/code> macro that comes with the <code>_com_ptr_t<\/code> library requires you to specify the <code>IID<\/code> that the alleged interface responds to. We give it a dummy interface ID 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>_com_ptr_t<\/code> passes the core functionality tests: The expected <code>AddRef<\/code> and <code>Release<\/code> operations are performed.<\/p>\n<p>Our test for receiving a new pointer simulates calling a function prototyped as<\/p>\n<pre>void GetTest(Test** out);\r\n<\/pre>\n<p>but instead of having to write a function that produces a <code>Test<\/code> object, we just put the parameter in a variable. That way, we can just step through our test in the debugger to confirm that everything behaves as desired.<\/p>\n<p>To receive a pointer into the wrapper, use the <code>&amp;<\/code> operator. This operator releases any previous wrapped pointer before receiving the new one.<\/p>\n<p>The <code>_com_ptr_t<\/code> fails the bonus test. The comparison operations fail with a compiler error saying that it couldn&#8217;t find a <code>Query\u00adInterface<\/code> method. That&#8217;s because <code>_com_ptr_t<\/code> uses COM identity, so it needs to <code>Query\u00adInterface<\/code> for the canonical unknown before performing the comparison.<\/p>\n<p>The <code>_com_ptr_t<\/code> fails the &#8220;accidental bypass&#8221; litmus test. 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 good way asks the wrapper to release the pointer. This nulls out the wrapped pointer in addition to calling its <code>Release()<\/code> method. On the other hand, the bad way asks the wrapper for the wrapped pointer, and then calls the <code>Release()<\/code> method directly on the wrapped pointer. This doesn&#8217;t update the wrapper, so when the wrapper destructs, you get a double-release.<\/p>\n<p>The fact that the member function to release the wrapped pointer is also called <code>Release<\/code> makes this an easy trap to fall into.<\/p>\n<p>The <code>_com_ptr_t<\/code> passes the litmus test: Both attempts to convert from another type of smart pointer generate an error which complains that there is no <code>Query\u00adInterface<\/code> method.<\/p>\n<p>So let&#8217;s keep a little scorecard.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th colspan=\"2\"><code>_com_ptr_t<\/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 MFC&#8217;s <code>IPTR<\/code>.<\/p>\n<p>\u00b9 Many of these libraries are quite old and predate the fancy-pants <code>reinterpret_cast<\/code> keyword. The only cast operator available at the time was the C-style cast, so that&#8217;s what they used.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As long as you fulfill the contract, based on method names.<\/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-108156","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>As long as you fulfill the contract, based on method names.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108156","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=108156"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108156\/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=108156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}