{"id":109325,"date":"2024-01-29T07:00:00","date_gmt":"2024-01-29T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109325"},"modified":"2024-01-29T12:07:55","modified_gmt":"2024-01-29T20:07:55","slug":"20240129-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240129-00\/?p=109325","title":{"rendered":"A comparison of various implementations of the Windows Runtime <CODE>IMemory&shy;Buffer<\/CODE>"},"content":{"rendered":"<p>In my studies of the <code>IMemory\u00adBuffer<\/code> interface, I found three implementations of that interface in the Windows Runtime.<\/p>\n<ul>\n<li><code>Windows.<wbr \/>Foundation.<wbr \/>MemoryBuffer<\/code>, obtained from <code>Buffer.<wbr \/>Create\u00adMemory\u00adBuffer\u00adOver\u00adIBuffer()<\/code>.<\/li>\n<li><code>Windows.<wbr \/>Graphics.<wbr \/>Imaging.<wbr \/>BitmapBuffer<\/code>, obtained from <code>Software\u00adBitmap.<wbr \/>Lock\u00adBuffer()<\/code>.<\/li>\n<li>Unnamed class obtained from <code>PerceptionFrame.<wbr \/>FrameData<\/code>.<\/li>\n<\/ul>\n<p><a title=\"How can I expose a pre-existing block of memory as a Windows Runtime object without copying the data?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240126-00\/?p=109322\"> We also wrote our own fourth implementation<\/a>, which we called <code>Custom\u00adMemory\u00adBuffer<\/code>, that lets you turn any block of memory into a <code>Memory\u00adBuffer<\/code>.<\/p>\n<p>All four of them behave differently. Let&#8217;s compare.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<th>Memory\u00adBuffer<\/th>\n<th>Bitmap\u00adBuffer<\/th>\n<th>Frame\u00adData<\/th>\n<th>Custom\u00adMemory\u00adBuffer<\/th>\n<\/tr>\n<tr>\n<td>Thread-safe?<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>IMemoryBuffer supports IMemoryBufferByteAccess?<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>CreateReference after Close<\/td>\n<td colspan=\"4\" align=\"center\">Empty<\/td>\n<\/tr>\n<tr>\n<td>Empty references raise Closed event?<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Raises Closed event automatically when released?<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Can extend lifetime during Closed event handler<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Buffer valid during Closed event?<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Can call methods during Closed event<\/td>\n<td>Yes<\/td>\n<td>Yes<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Buffer of empty or closed reference<\/td>\n<td colspan=\"4\" align=\"center\">pointer = <code>nullptr<\/code> and size = 0<\/td>\n<\/tr>\n<tr>\n<td>Memory freed when&#8230;<\/td>\n<td colspan=\"4\" align=\"center\">IMemory\u00adBuffer and all IMemory\u00adBuffer\u00adReferences have been closed or destructed<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>All happy memory buffers look alike. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Anna_Karenina_principle\"> Each unhappy memory buffer is unhappy in its own way<\/a>.<\/p>\n<p>The standard <code>Memory\u00adBuffer<\/code> has the problem of not being thread-safe. If you call <code>Close<\/code> at the same time as <code>Create\u00adReference<\/code>, you may experience use-after-free bugs. And if you call <code>Close<\/code> twice simultaneously, you can add to your woes null pointer crashes, over-release of the underlying <code>IBuffer<\/code>, and double-raising of the the <code>Closed<\/code> event, depending on exactly how the race plays out.<\/p>\n<p>All four implementations agree that if you call <code>Create\u00adReference<\/code> on a closed <code>IMemory\u00adBuffer<\/code>, you get an &#8220;empty reference&#8221;. An empty reference is one that protects no memory. If you ask for the buffer of an empty reference, you get a null pointer and a size of zero.<\/p>\n<p>In all of the implementations except <code>Frame\u00adData<\/code>, empty references raise the <code>Closed<\/code> event.<\/p>\n<p>The <code>Bitmap\u00adBuffer<\/code>&#8216;s memory buffer reference raises the <code>Closed<\/code> event only on an explicit call to <code>Closed<\/code>. The others raise the <code>Closed<\/code> event either on explicit closure or when the last reference is released. This means that <code>Bitmap\u00adBuffer<\/code> reference&#8217;s <code>Closed<\/code> event is even more unreliable than the <code>Closed<\/code> event already is by its nature.<\/p>\n<p>The <code>Memory\u00adBuffer<\/code> and <code>Frame\u00adData<\/code> ignore attempts by the <code>Closed<\/code> event handler to extend the reference&#8217;s lifetime. The biggest consequence of this is that the <code>Closed<\/code> event in those implementations <a title=\"The dangerous implementations of the IMemoryBufferReference.Closed event\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240124-00\/?p=109311\"> will corrupt memory if consumed from a GC language<\/a>. The <code>Bitmap\u00adBuffer<\/code> sneakily passes this test because it is masked by the other defect of simply not raising the <code>Closed<\/code> event in the dangerous scenario in the first place.<\/p>\n<p>The <code>Bitmap\u00adBuffer<\/code> and <code>Frame\u00adData<\/code> raise the <code>Closed<\/code> event after freeing the memory, which means that the event is useless for triggering cleanup: Since you are told that the memory has been freed only after it happened, all you&#8217;re really learning is that &#8220;Oops, you already corrupted memory.&#8221;<\/p>\n<p>The <code>Frame\u00adData<\/code> has the bonus insult of passing you an <code>IMemory\u00adBuffer\u00adReference<\/code> in the <code>Closed<\/code> handler that cannot be used! Any attempt to obtain the buffer&#8217;s capacity or pointer will hang. (That&#8217;s because it raises the <code>Closed<\/code> event while still holding its internal lock. Calling to outside code while holding a lock is a bad idea for reasons like this.)<\/p>\n<p>Our <code>Custom\u00adMemory\u00adBuffer<\/code> tries to avoid all of these little defects.<\/p>\n<p>But what if you are forced to use one of the other three implementations of <code>IMemory\u00adBuffer<\/code>, or some other fifth implementation from an external source that isn&#8217;t even on the list. Seeing as the first three attempts at implementing <code>IMemory\u00adBuffer<\/code> all failed in different ways, what confidence do you have that an unknown implementation will be well-behaved?<\/p>\n<p>We&#8217;ll solve this problem next time. The answer is right under our nose.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Every unhappy class is unhappy in its own way.<\/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-109325","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Every unhappy class is unhappy in its own way.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109325","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=109325"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109325\/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=109325"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109325"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109325"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}