{"id":5021,"date":"2015-11-04T13:00:13","date_gmt":"2015-11-04T13:00:13","guid":{"rendered":"http:\/\/writeasync.net\/?p=5021"},"modified":"2015-11-04T05:16:45","modified_gmt":"2015-11-04T05:16:45","slug":"dry-raii-with-autobuffer","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5021","title":{"rendered":"DRY RAII with AutoBuffer"},"content":{"rendered":"<p><a href=\"http:\/\/www.c2.com\/cgi\/wiki?DontRepeatYourself\">Don&#8217;t Repeat Yourself<\/a> is a good maxim in software design &#8212; avoid duplication of information. Today&#8217;s sample code will show a small fix to a problem which quite literally involves repeating yourself: those annoying &#8220;call twice&#8221; Win32 functions like <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/dd323672(v=vs.85).aspx\"><code>GetVirtualDiskPhysicalPath<\/code><\/a>.<\/p>\n<p>Here&#8217;s an example of how one might call it in &#8220;old school&#8221; C-style code:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nULONG size = 16;\r\nWCHAR * path = new WCHAR&#x5B;size];\r\nDWORD error = GetVirtualDiskPhysicalPath(handle, &amp;size, path);\r\nif (error == ERROR_INSUFFICIENT_BUFFER)\r\n{\r\n    \/\/ Sigh. Call again now that we know the real size to use...\r\n    delete &#x5B;] path;\r\n    path = new WCHAR&#x5B;size];\r\n    error = GetVirtualDiskPhysicalPath(handle, &amp;size, path)\r\n}\r\n\r\n\/\/ ... more code here, which will hopefully eventually free the buffer ...\r\n<\/pre>\n<p>There are multiple problems here. The duplication is ugly. The naked memory management calls leave you prone to leaks if you don&#8217;t remember to free on every return path. This &#8220;call twice to get buffer size&#8221; is also a common pattern for many other Win32 calls, so what you do here has to be structurally repeated in many places.<\/p>\n<p>With C++11 (or better), you can elegantly solve this problem with judicious use of <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/dd293608.aspx\">lambda expressions<\/a>, STL <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/9xd04bzs.aspx\">vectors<\/a>, and templating. Here is my stab at it in a class called <code>AutoBuffer<\/code>. It attempts to provide a type-safe and <a href=\"http:\/\/www.hackcraft.net\/raii\/\">RAII-observant<\/a> solution to the problem above.<\/p>\n<p>Let&#8217;s rewrite the previous sample code using <code>AutoBuffer<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nAutoBuffer&lt;WCHAR&gt; buffer;\r\nDWORD error = buffer.Fill&lt;ULONG&gt;(&#x5B;handle](WCHAR * dataPtr, PULONG sizePtr)\r\n{\r\n    return GetVirtualDiskPhysicalPath(handle, sizePtr, dataPtr);\r\n});\r\n\r\n\/\/ more code here... but no need to free the buffer, since AutoBuffer will destroy it for you!\r\n<\/pre>\n<p>Much simpler, eh? No more duplication and a lower chance of bugs.<\/p>\n<p>Below is a reference implementation with unit tests that takes care of the &#8220;return error code&#8221; variation of this pattern, as demonstrated above. (Note that there are other Win32 functions using the &#8220;call twice&#8221; semantics such as <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/ms684932(v=vs.85).aspx\"><code>QueryServiceConfig<\/code><\/a> that instead return <code>BOOL<\/code> and <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/ms679360(v=vs.85).aspx\"><code>GetLastError<\/code><\/a> must be used; I&#8217;ll leave that one as an exercise for the reader&#8230;.)<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ Implementation -- AutoBuffer.h\r\n#pragma once\r\n\r\n#include &lt;Windows.h&gt;\r\n#include &lt;vector&gt;\r\n#include &lt;functional&gt;\r\n\r\ntemplate&lt;typename TData&gt;\r\nclass AutoBuffer\r\n{\r\npublic:\r\n    AutoBuffer()\r\n        : buffer_(sizeof(TData), '&#92;&#48;')\r\n    {\r\n    }\r\n\r\n    ~AutoBuffer()\r\n    {\r\n    }\r\n\r\n    template&lt;typename TSize&gt;\r\n    DWORD Fill(std::function&lt;DWORD(TData *, TSize *)&gt; func)\r\n    {\r\n        TSize size = static_cast&lt;TSize&gt;(get_Size());\r\n        DWORD result = func(Ptr(0), &amp;size);\r\n        if (result == ERROR_INSUFFICIENT_BUFFER)\r\n        {\r\n            buffer_.resize(size);\r\n            result = func(Ptr(0), &amp;size);\r\n        }\r\n\r\n        return result;\r\n    }\r\n\r\n    size_t get_Size() const\r\n    {\r\n        return buffer_.size();\r\n    }\r\n\r\n    TData * operator-&gt;()\r\n    {\r\n        return Ptr(0);\r\n    }\r\n\r\n    TData &amp; operator&#x5B;](int index)\r\n    {\r\n        return *Ptr(index);\r\n    }\r\n\r\nprivate:\r\n    std::vector&lt;unsigned char&gt; buffer_;\r\n\r\n    TData * Ptr(int index)\r\n    {\r\n        return reinterpret_cast&lt;TData *&gt;(&amp;buffer_&#x5B;index * sizeof(TData)]);\r\n    }\r\n};\r\n<\/pre>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ Tests -- AutoBufferTest.cpp\r\n#include &quot;CppUnitTest.h&quot;\r\n#include &quot;AutoBuffer.h&quot;\r\n\r\nusing namespace Microsoft::VisualStudio::CppUnitTestFramework;\r\n\r\nnamespace UnitTest1\r\n{\r\n    TEST_CLASS(AutoBufferTest)\r\n    {\r\n    public:\r\n        struct ExampleData\r\n        {\r\n            int Number;\r\n            void * Ptr;\r\n        };\r\n\r\n\r\n        TEST_METHOD(ShouldInitializeWithSizeOfOneItemEmpty)\r\n        {\r\n            AutoBuffer&lt;ExampleData&gt; buffer;\r\n\r\n            Assert::AreEqual(sizeof(ExampleData), buffer.get_Size());\r\n            Assert::AreEqual(0, buffer-&gt;Number);\r\n            Assert::IsNull(buffer-&gt;Ptr);\r\n        }\r\n\r\n        TEST_METHOD(ShouldFillAndReturnSuccessWhenFirstCallSucceeds)\r\n        {\r\n            AutoBuffer&lt;ExampleData&gt; buffer;\r\n\r\n            DWORD result = buffer.Fill&lt;ULONG&gt;(&#x5B;](ExampleData * dataPtr, PULONG sizePtr)\r\n            {\r\n                dataPtr-&gt;Number = *sizePtr;\r\n                return ERROR_SUCCESS;\r\n            });\r\n\r\n            Assert::AreEqual(static_cast&lt;DWORD&gt;(0), result);\r\n            Assert::AreEqual(static_cast&lt;int&gt;(sizeof(ExampleData)), buffer-&gt;Number);\r\n        }\r\n\r\n        TEST_METHOD(ShouldReturnInitialErrorWhenFirstCallFails)\r\n        {\r\n            AutoBuffer&lt;ExampleData&gt; buffer;\r\n\r\n            DWORD result = buffer.Fill&lt;ULONG&gt;(&#x5B;](ExampleData * dataPtr, PULONG sizePtr)\r\n            {\r\n                return ERROR_NOT_FOUND;\r\n            });\r\n\r\n            Assert::AreEqual(static_cast&lt;DWORD&gt;(ERROR_NOT_FOUND), result);\r\n            Assert::AreEqual(0, buffer-&gt;Number);\r\n        }\r\n\r\n        TEST_METHOD(ShouldCallAgainWithUpdatedSizeIfFirstCallFailsWithInsufficientBuffer)\r\n        {\r\n            AutoBuffer&lt;ExampleData&gt; buffer;\r\n\r\n            DWORD result = buffer.Fill&lt;ULONG&gt;(&#x5B;](ExampleData * dataPtr, PULONG sizePtr)\r\n            {\r\n                if (++dataPtr-&gt;Number == 1)\r\n                {\r\n                    *sizePtr *= 2;\r\n                    return ERROR_INSUFFICIENT_BUFFER;\r\n                }\r\n\r\n                dataPtr&#x5B;1].Number = 1000;\r\n                return ERROR_SUCCESS;\r\n            });\r\n\r\n            Assert::AreEqual(static_cast&lt;DWORD&gt;(0), result);\r\n            Assert::AreEqual(2, buffer-&gt;Number);\r\n            Assert::AreEqual(1000, buffer&#x5B;1].Number);\r\n        }\r\n\r\n        TEST_METHOD(ShouldReturnFinalErrorIfSecondCallFails)\r\n        {\r\n            AutoBuffer&lt;ExampleData&gt; buffer;\r\n\r\n            DWORD result = buffer.Fill&lt;ULONG&gt;(&#x5B;](ExampleData * dataPtr, PULONG sizePtr)\r\n            {\r\n                if (++dataPtr-&gt;Number == 1)\r\n                {\r\n                    return ERROR_INSUFFICIENT_BUFFER;\r\n                }\r\n\r\n                return ERROR_NOT_FOUND;\r\n            });\r\n\r\n            Assert::AreEqual(static_cast&lt;DWORD&gt;(ERROR_NOT_FOUND), result);\r\n        }\r\n    };\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Don&#8217;t Repeat Yourself is a good maxim in software design &#8212; avoid duplication of information. Today&#8217;s sample code will show a small fix to a problem which quite literally involves repeating yourself: those annoying &#8220;call twice&#8221; Win32 functions like GetVirtualDiskPhysicalPath.&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[91,101],"tags":[],"class_list":["post-5021","post","type-post","status-publish","format-standard","hentry","category-design","category-native"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5021","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5021"}],"version-history":[{"count":0,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5021\/revisions"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5021"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5021"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5021"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}