<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	
  <title>The Old New Thing - Dreamcatcher Edition</title>
	<atom:link href="https://devblogs.microsoft.com/oldnewthing/feed" rel="self" type="application/rss+xml" />
	<link>https://devblogs.microsoft.com/oldnewthing</link>
	<description>Practical development throughout the evolution of Windows.</description>
	<lastBuildDate>Thu, 02 Jul 2026 16:12:51 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2021/03/Microsoft-Favicon.png</url>
	
  <title>The Old New Thing - Dreamcatcher Edition</title>
	<link>https://devblogs.microsoft.com/oldnewthing</link>
	<width>32</width>
	<height>32</height>
</image> 
	
  <item>
    <title>The case of the thread executing from an unloaded third-party DLL</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260702-00/?p=112500</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260702-00/?p=112500#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Thu, 02 Jul 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Code]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112500</guid>
    <content:encoded><![CDATA[<p>The Explorer team was investigating a crash that was occuring at a relatively high rate and found that it took the form of a thread executing from an unloaded third-party DLL.</p>
<pre>0:173&gt; k
RetAddr               Call Site
00000000`557c5820     &lt;Unloaded_LibUtils_CloudNs_3.dll&gt;+0x265fe
00000000`00000008     &lt;Unloaded_LibUtils_CloudNs_3.dll&gt;+0x2b5820
00000000`0000000e     0x8
00000000`00000008     0xe
00000000`557c8c18     0x8
ffffffff`fffffffe     &lt;Unloaded_LibUtils_CloudNs_3.dll&gt;+0x2b8c18
00000000`00000000     0xffffffff`fffffffe
</pre>
<p>There isn&#8217;t much on the stack at all.</p>
<pre>0:173&gt; dps @rsp
00000000`1248f920  00000000`557c5820 &lt;Unloaded_LibUtils_CloudNs_3.dll&gt;+0x2b5820
00000000`1248f928  00000000`00000008
00000000`1248f930  00000000`0000000e
00000000`1248f938  00000000`00000008
00000000`1248f940  00000000`557c8c18 &lt;Unloaded_LibUtils_CloudNs_3.dll&gt;+0x2b8c18
00000000`1248f948  ffffffff`fffffffe
00000000`1248f950  00000000`00000000
00000000`1248f958  00000000`00000000
00000000`1248f960  00000000`00000000
00000000`1248f968  00000000`00000000
00000000`1248f970  00000000`00000000
00000000`1248f978  00000000`00000000
00000000`1248f980  00000000`00000000
00000000`1248f988  00007ff9`a2117344 kernel32!BaseThreadInitThunk+0x14
00000000`1248f990  00000000`00000000
00000000`1248f998  00000000`00000000
</pre>
<p>This is just a worker thread the operates entirely inside <tt>LibDB.CloudNs.3.dll</tt>. It doesn&#8217;t have a very deep stack, so I suspect that it&#8217;s idle and is waiting for work to do.</p>
<p>For these types of investigations, there usually isn&#8217;t much to see directly in the crashing thread. That thread is the victim. You have to do additional research to figure out who unloaded the DLL prematurely.</p>
<p>Some snooping around found another stack that involves this unloaded DLL:</p>
<pre>0:159&gt; k
RetAddr               Call Site
00007ff9`9fdbbea0     ntdll!ZwWaitForMultipleObjects+0x14
00007ff9`9fdbbd9e     KERNELBASE!WaitForMultipleObjectsEx+0xf0
00000000`554d65fe     KERNELBASE!WaitForMultipleObjects+0xe
00000000`55765820     &lt;Unloaded_LibDB_CloudNs_3.dll&gt;+0x965fe
00000000`00000003     &lt;Unloaded_LibUtils_JsonNs_3.dll&gt;+0x255820
00000000`00000004     0x3
00000000`00000008     0x4
00000000`55768c18     0x8
ffffffff`fffffffe     &lt;Unloaded_LibUtils_CloudNs_3.dll&gt;+0x258c18
00000000`00000000     0xffffffff`fffffffe
</pre>
<p>The most recently unloaded DLLs are</p>
<pre>00007ff9`6d7c0000 00007ff9`6d80a000   FabrikamContextMenu.dll
00007ff9`115e0000 00007ff9`1172f000   LitWareSync.dll
00007ff9`643d0000 00007ff9`64681000   CcNamespace.dll
00000000`55440000 00000000`5550b000   LibDB_CloudNs_3.dll
00000000`55860000 00000000`55998000   LibNet_CloudNs_3.dll
00000000`557f0000 00000000`5585b000   LibJson_CloudNs_3.dll
00000000`55510000 00000000`557e7000   LibUtils_CloudNs_3.dll
00000000`561a0000 00000000`56238000   MSVCP100.dll
00000000`56240000 00000000`56312000   MSVCR100.dll
00007ff9`85130000 00007ff9`85167000   EhStorShell.dll
00007ff9`3cac0000 00007ff9`3cb61000   wpdshext.dll
00007ff9`78a00000 00007ff9`78a26000   EhStorAPI.dll
00007ff9`686f0000 00007ff9`68754000   PlayToDevice.dll
00007ff9`67110000 00007ff9`6718d000   provsvc.dll
</pre>
<p>So the <tt>LibDB.CloudNs.3.dll</tt> that got unloaded is just part of an entire ecosystem of <tt>Lib*.CloudNs.3.dll</tt> dynamic libraries that all got unloaded together.</p>
<p>The ringleader of this operation appears to be <tt>CcNamespace.dll</tt>, which looks like the Contoso namespace extension that adds a &#8220;Contoso&#8221; node under <span style="text-decoration: line-through;"><i>My Computer</i></span> <i>This PC</i> that gives you a view into all your Contoso things stored in the Contoso cloud service. All the other DLLs are helpers that the main <tt>CcNamespace.dll</tt> uses to accomplish its tasks.</p>
<p>The main <tt>CcNamespace.dll</tt> was loaded by Explorer as a shell extension, and its <code>Dll­Can­Unload­Now</code> function was returning <code>S_OK</code> when there were no active references to objects in <tt>CcNamespace.dll</tt>. Unfortunately, when it said &#8220;Sure, it&#8217;s safe to unload me&#8221;, that linchpin DLL unloaded all its minions, unaware that one of the minions (the utility library) had spun up some worker threads.</p>
<p>You might think that the fix is to update the utility library&#8217;s <code>Dll­Can­Unload­Now</code> to return <code>S_FALSE</code> if there are still busy background threads.¹ But that doesn&#8217;t work because the utility library is probably not a COM DLL in the first place. It&#8217;s just a traditional DLL that <tt>CcNamespace.dll</tt> uses, and it is <tt>CcNamespace.dll</tt> that is the COM DLL.</p>
<p>The <code>Dll­Can­Unload­Now</code> in <tt>CcNamespace.dll</tt> could warn <tt>LibUtils.CloudNs.3.dll</tt> that it should start winding down, but you&#8217;re basically in a tricky spot because the <code>DLL_<wbr />PROCESS_<wbr />ATTACH</code> cannot wait for the worker thread to exit.</p>
<p>I think the way to go is for the worker thread to increment the DLL reference count when it starts its worker thread, and to use <code>Free­Library­And­Exit­Thread</code> to exit the worker thread. Alternatively, it could make its worker thread a threadpool thread and use <code>Free­Library­When­Callback­Returns</code> to request that the system decrement the DLL reference count when it finishes.</p>
<p>This is probably something the utility library should have done anyway. I suspect that the worker thread is not something that clients of the utility library are even aware of. It is just an implementation detail of the utility library, created without the knowledge of the main DLL.</p>
<p>Fortunately, the application compatibility team has a copy of Contoso Cloud in their library, so even though we couldn&#8217;t reproduce the crash, we were still able to confirm that <tt>CcNamespace.dll</tt> is indeed the shell extension DLL whose unloading triggers the unloading of all the dependent DLLs.</p>
<p>We were about to contact Contoso with our conclusions and suggestions for improvement, but we discovered that it would be pointless because Contoso discontinued that namespace extension years ago. They replaced it with a different way of integrating their cloud content into Windows; the only people using the namespace extension are those who still using an old version, either because they don&#8217;t want to pay for the upgrade, or because they are actively avoiding the upgrade because they like the old way.</p>
<p>Those customers are using a product that has gone out of support. Contoso doesn&#8217;t care about those old customers any more. Windows will have to fix it without Contoso&#8217;s help.</p>
<p>The Explorer team added an application compatibility flag for the Contoso Cloud namespace extension to say &#8220;When you load this shell extension, do a <code>Get­Module­Handle­Ex</code> with the <code>GET_<wbr />MODULE_<wbr />HANDLE_<wbr />EX_<wbr />FLAG_<wbr />PIN</code> flag so the DLL never unloads.&#8221; That way, even if the DLL says &#8220;Sure, go ahead and unload me, it&#8217;s totally safe, trust me,&#8221; and COM does a <code>FreeLibrary</code>, the DLL doesn&#8217;t actually unload.</p>
<p>¹ Even if you manage to get return <code>Dll­Can­Unload­Now</code> to return <code>S_FALSE</code>, it doesn&#8217;t help if COM is being uninitialized. In that case, <a title="CoUninitalize will ask a DLL if it is okay to unload now, but the answer is a foregone conclusion" href="https://devblogs.microsoft.com/oldnewthing/20131106-00/?p=2723"> <code>CoUninitalize</code> will ask a DLL if it is okay to unload now, but the answer is a foregone conclusion</a>: If COM is shutting down, COM is going to unload all the DLLs that it loaded. It asks you if you are okay with it, not because it cares what your answer is, but to give you a chance to do cleanup outside of <code>DllMain</code>.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260702-00/?p=112500">The case of the thread executing from an unloaded third-party DLL</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260702-00/?p=112500/feed</wfw:commentRss>
    <slash:comments>4</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>It rather involved being on the other side of this airtight hatchway: Changing administrative settings</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260701-00/?p=112498</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260701-00/?p=112498#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Wed, 01 Jul 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Other]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112498</guid>
    <content:encoded><![CDATA[<p>A security vulnerability report arrived that went roughly like this:</p>
<blockquote class="q"><p>An attacker can bypass security policies by modifying the following registry keys to disable ⟦security feature 1⟧ and ⟦security feature 2⟧.</p></blockquote>
<p>The statement is true, but what they don&#8217;t mention is that administrator privileges are required to modify those keys. This is like saying that a door lock is insecure because you can open the door from the inside. If you are inside, then you have already gotten past the door!</p>
<p>Indeed, the purpose of those keys is to define the security policy in the first place! So it boils down to &#8220;It&#8217;s a security vulnerability that an administrator can change a security policy.&#8221;</p>
<p>What the security researcher found was that if your system has been compromised, the first guy who gets into your inner sanctum can make your system even more vulnerable.¹ If you assume that the attacker has full control, then it&#8217;s not surprising that they control everything.</p>
<p>¹ Isn&#8217;t this the plot to half of the sci-fi movies ever made? The plucky hero sneaks behind enemy lines in order to disable the bad guys&#8217; shields long enough to let the rest of the team in. This isn&#8217;t a security flaw in the shields. It&#8217;s a security flaw in whatever was supposed to protect the switch that turns off the shields.</p>
<p>The sci-fi movie analogy would be &#8220;If we can get to the switch that turns off the shields, then we can turn them off!&#8221;</p>
<p>Well, yeah. The hard part is getting into the room that has the switch.</p>
<p>It rather involved being on the other side of this airtight hatchway.</p>
<p><b>Bonus chatter</b>: This is a repeat of <a title="It rather involved being on the other side of the airtight hatchway: Disabling a security feature as an administrator" href="https://devblogs.microsoft.com/oldnewthing/20240806-00/?p=110103"> It rather involved being on the other side of the airtight hatchway: Disabling a security feature as an administrator</a>, but this type of bogus vulnerability report happens so much, I wrote it up again before I realized that it was a duplicate.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260701-00/?p=112498">It rather involved being on the other side of this airtight hatchway: Changing administrative settings</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260701-00/?p=112498/feed</wfw:commentRss>
    <slash:comments>5</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>2026 mid-year link clearance</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260630-01/?p=112494</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260630-01/?p=112494#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Tue, 30 Jun 2026 14:00:01 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Non-Computer]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112494</guid>
    <content:encoded><![CDATA[<p>Oh boy, more random stuff.</p>
<ul>
<li><a href="https://aresluna.org/the-hardest-working-font-in-manhattan/"> The hardest working font in Manhattan</a>: <a href="https://shifthappens.site/">Marcin Wichary</a> digs up the history of the font named Gorton. I grew up with this font, seeing it not only on punch card keyboards but also in a Leroy lettering kit that we used in sixth grade graphics class when we studied mechanical drawing.</li>
<li><a title="RollerCoaster Tycoon's Overengineered Puking System" href="https://www.youtube.com/watch?v=tG66j1sP8gs"> RollerCoaster Tycoon&#8217;s Overengineered Puking System</a>. Learn more than you ever needed to know about the algorithms in the game RollerCoaster Tycoon that determine when a park guest pukes. No really.</li>
<li><a href="https://sickos-newsletter.beehiiv.com/p/the-spaghetti-policy-for-all-mlb-teams"> The Spaghetti Policy for All MLB Teams</a> by the appropriately named Sickos Committee breaks down the outside food policies for all of teams in Major League Baseball to determine which ones would let you bring in a gallon of spaghetti. The first part is fairly straightforward, but it gets far more weird (and sicko) as they look at the stadiums where a gallon of spaghetti might not be permitted. (Background information: Sports fans in Philadelphia have a reputation for poor behavior. These are the people who <a href="https://en.wikipedia.org/wiki/Philadelphia_Eagles_Santa_Claus_incident"> booed Santa Claus and pelted him with snowballs</a>.)</li>
<li>We have reached the second generation now. &#8220;<a href="https://www.windowscentral.com/microsoft/microsoft-invented-escrow-builds-to-launch-functional-apps" rel="nofollow">Microsoft invented &#8220;escrow builds&#8221; to launch functional apps &#8211; that&#8217;s internal &#8216;Microspeak&#8217; jargon for quality control</a>&#8221; is a rehash of <a title="Microspeak elaborated: Isn't escrow just a release candidate by another name?" href="https://devblogs.microsoft.com/oldnewthing/20260623-00/?p=112462"> my article on the history of the term &#8220;escrow&#8221;</a>, but the article appears to just be a suspicious mishmash of fragments from the article. For example, in the sentence &#8220;He described it as unhelpful because the blog essentially described a metaphor using another metaphor&#8221; the antecedents of &#8220;it&#8221; and &#8220;the blog&#8221; are not present in the article, leading the reader to think that &#8220;it&#8221; is the explanation of what <i>escrow</i> means and that &#8220;the blog&#8221; is my blog. On top of that, the title of the article introduces the phrase &#8220;launch functional apps&#8221; (whatever that means), even though none of those three words appear anywhere in the original article, nor in the dodgy rehash. Undaunted, a different AI content farm picked up on that article and somehow expanded it <a href="https://windowsforum.com/threads/microsoft-escrow-build-and-bake-time-a-practical-release-guide.402725/" rel="nofollow&quot;"> into an even longer article</a> that conveys even less information.</li>
</ul>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260630-01/?p=112494">2026 mid-year link clearance</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260630-01/?p=112494/feed</wfw:commentRss>
    <slash:comments>1</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>A compatibility note on the abuse of Windows window class extra bytes</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260630-00/?p=112488</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260630-00/?p=112488#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Tue, 30 Jun 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[History]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112488</guid>
    <content:encoded><![CDATA[<p>During my discussion of <a title="The evolution of Windows window and class extra bytes in Windows" href="https://devblogs.microsoft.com/oldnewthing/20260629-00/?p=112484"> the evolution of system-windows window and class extra bytes</a>, I noted that even though IDs are typically small integers, people liked to stash pointers there, so we had to expand the ID field to a pointer-sized integer.</p>
<p>One thing I&#8217;ve learned is that anywhere it&#8217;s possible to hide a pointer, people will hide a pointer there. This is true even for small integers.</p>
<p>As I was digging up the history of the extra bytes, I saw a special note in the 16-bit code for <code>Set­Class­Word</code>: It says that there&#8217;s an app that expects to be able to modify the value of <code>GWW_<wbr />CB­CLS­EXTRA</code>.</p>
<p>Now, modifying this value has no practical effect because the memory for the class was allocated when you called <code>Register­Class</code>. You can&#8217;t go back in time and change the allocation size.</p>
<p>But one program realized that it could use this value as a place to store some private data, so they did. Sure, that&#8217;s not the purpose of the <code>GWW_<wbr />CB­CLS­EXTRA</code>, but that never stopped them.</p>
<p>For compatibility, Windows lets 16-bit programs modify <code>GWW_<wbr />CB­CLS­EXTRA</code>. But at least it blocks it for 32-bit and 64-bit programs. One loophole closed. Countless more to go.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260630-00/?p=112488">A compatibility note on the abuse of Windows window class extra bytes</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260630-00/?p=112488/feed</wfw:commentRss>
    <slash:comments>5</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>The evolution of window and class extra bytes in Windows</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260629-00/?p=112484</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260629-00/?p=112484#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Mon, 29 Jun 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Code]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112484</guid>
    <content:encoded><![CDATA[<p>Windows provides a family of functions for accessing so-called &#8220;extra bytes&#8221;. There are two categories of extra bytes: Class extra bytes (which belong to the window class) and window extra bytes (which belong to each window created from that class). Applications can request extra bytes at class registration, and those are accessed at increasing offsets starting at zero. The system also defines a number of extra bytes, and those use negative offsets.</p>
<p>We&#8217;re going to look at the system-defined offsets.</p>
<p>In 16-bit Windows, these were the available extra bytes and the function you used to read them:</p>
<table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Name</th>
<th>Size</th>
<th>Accessor</th>
<th>Notes</th>
</tr>
<tr>
<td><tt>GCW_MENUNAME</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_HBRBACKGROUND</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_HCURSOR</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_HICON</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_HMODULE</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_CBWNDEXTRA</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_CBCLSEXTRA</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCL_WNDPROC</tt></td>
<td>int32_t</td>
<td><tt>GetClassLong</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_STYLE</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GCW_ATOM</tt></td>
<td>int16_t</td>
<td><tt>GetClassWord</tt></td>
<td>Added in Windows 3.1</td>
</tr>
<tr>
<td><tt>GWL_WNDPROC</tt></td>
<td>int32_t</td>
<td><tt>GetWindowLong</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GWW_HINSTANCE</tt></td>
<td>int16_t</td>
<td><tt>GetWindowWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GWW_HWNDPARENT</tt></td>
<td>int16_t</td>
<td><tt>GetWindowWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GWW_ID</tt></td>
<td>int16_t</td>
<td><tt>GetWindowWord</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GWL_STYLE</tt></td>
<td>int32_t</td>
<td><tt>GetWindowLong</tt></td>
<td>&nbsp;</td>
</tr>
<tr>
<td><tt>GWL_EXSTYLE</tt></td>
<td>int32_t</td>
<td><tt>GetWindowLong</tt></td>
<td>Added in Windows 3.0</td>
</tr>
<tr>
<td><tt>DWL_MSGRESULT</tt></td>
<td>int32_t</td>
<td><tt>GetWindowLong</tt></td>
<td>For dialog windows</td>
</tr>
<tr>
<td><tt>DWL_DLGPROC</tt></td>
<td>int32_t</td>
<td><tt>GetWindowLong</tt></td>
<td>For dialog windows</td>
</tr>
<tr>
<td><tt>DWL_USER</tt></td>
<td>int32_t</td>
<td><tt>GetWindowLong</tt></td>
<td>For dialog windows</td>
</tr>
</tbody>
</table>
<p>There is clearly a naming pattern here for class and window bytes.</p>
<p>The first letter <tt>G</tt> stands for <tt>Get</tt>. The second letter <tt>C</tt> or <tt>W</tt> stands for <tt>Class</tt> or <tt>Window</tt>. And the third letter <tt>W</tt> or <tt>L</tt> stands for <tt>Word</tt> or <tt>Long</tt>.¹</p>
<p>For window bytes that apply only to dialog windows, the first letter changes to <tt>D</tt> for &#8220;dialog&#8221;. These values are zero or positive, since they are really just extra bytes registered to the standard dialog class.</p>
<p>Now, in 16-bit Windows, handles were 16-bit values, but in 32-bit Windows, they expand to 32-bit values, so 32-bit Windows changed the functions from <code>Get­Something­<span style="border: solid 1px currentcolor;">Word</span></code> to <code>Get­Something­<span style="border: solid 1px currentcolor;">Long</span></code>, and the prefixes correspondingly changed from <tt>W</tt> to from <tt>L</tt>. So our table now looks like this:</p>
<table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Name</th>
<th>16-bit prefix/size</th>
<th>32-bit prefix/size</th>
</tr>
<tr>
<td><tt>MENUNAME</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>HBRBACKGROUND</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>HCURSOR</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>HICON</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>HMODULE</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>CBWNDEXTRA</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>CBCLSEXTRA</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>WNDPROC</tt></td>
<td><tt>GCL_</tt> int32_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>STYLE</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>ATOM</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCW_</tt> int16_t</td>
</tr>
<tr>
<td><tt>HICONSM</tt></td>
<td>&nbsp;</td>
<td><tt>GCL_</tt> int32_t <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td><tt>WNDPROC</tt></td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>HWNDPARENT</tt></td>
<td><tt>GWW_</tt> int16_t</td>
<td><tt>GWL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>ID</tt></td>
<td><tt>GWW_</tt> int16_t</td>
<td><tt>GWL_</tt> int32_t ◱</td>
</tr>
<tr>
<td><tt>STYLE</tt></td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>EXSTYLE</tt></td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>USERDATA</tt></td>
<td>&nbsp;</td>
<td><tt>GWL_</tt> int32_t <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td><tt>MSGRESULT</tt></td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>DLGPROC</tt></td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>USER</tt></td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWL_</tt> int32_t</td>
</tr>
</tbody>
</table>
<p>The ◱ symbol represents a value that got bigger, and the <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> symbol represents values that did not exist in 16-bit Windows.</p>
<p>Even though control IDs are typically small integers, the space for them was expanded from a 16-bit value to a 32-bit value because <a title="Why are timer IDs and dialog control IDs 64-bit values on 64-bit Windows? Did you really expect people to create more than 4 billion timers or dialog controls?" href="https://devblogs.microsoft.com/oldnewthing/20191010-00/?p=102978"> some people were using it to hold pointers or handles</a>. (One way to create a process-wide unique number is to <a title="A window can't have two timers with the same ID, so how do I assign an ID that nobody else is using?" href="https://devblogs.microsoft.com/oldnewthing/20191009-00/?p=102974"> allocate memory and use its address</a>.)</p>
<p>The next step in the evolution of extra bytes is the conversion from 32-bit to 64-bit Windows. Pointers and handles expand to 64-bit values on 64-bit Windows, so all of the extra bytes that are used to (or could be used to) hold a handle or pointer were expanded to a 64-bit version.</p>
<p>To make it possible to write code that targets both 32-bit and 64-bit Windows, the design of 64-bit Windows didn&#8217;t make the hard break that 32-bit Windows did from 16-bit Windows. Instead, they introduced new functions that accept pointer-sized integers, which are 32-bit values on 32-bit Windows and 64-bit values on 64-bit Windows. That way, you just use those new functions everywhere, and they will expand on 64-bit systems and remain the same on 32-bit systems.</p>
<p>The new functions have names like <code>Get­Window­Long­<span style="border: solid 1px currentcolor;">Ptr</span></code>, and the corresponding prefixes were changed to <code>GWLP_</code> and so on.</p>
<table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Name</th>
<th>16-bit prefix/size</th>
<th>32-bit prefix/size</th>
<th>32/64-bit prefix/size</th>
</tr>
<tr>
<td><tt>MENUNAME</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>HBRBACKGROUND</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>HCURSOR</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>HICON</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>HMODULE</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>CBWNDEXTRA</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>CBCLSEXTRA</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>WNDPROC</tt></td>
<td><tt>GCL_</tt> int32_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>STYLE</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCL_</tt> int32_t ◱</td>
<td><tt>GCL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>ATOM</tt></td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCW_</tt> int16_t</td>
<td><tt>GCW_</tt> int16_t</td>
</tr>
<tr>
<td><tt>HICONSM</tt></td>
<td>&nbsp;</td>
<td><tt>GCL_</tt> int32_t <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><tt>GCLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>WNDPROC</tt></td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t ◱</td>
<td><tt>GWLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>HWNDPARENT</tt></td>
<td><tt>GWW_</tt> int16_t</td>
<td><tt>GWL_</tt> int32_t ◱</td>
<td><tt>GWLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>ID</tt></td>
<td><tt>GWW_</tt> int16_t</td>
<td><tt>GWL_</tt> int32_t ◱</td>
<td><tt>GWLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>STYLE</tt></td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>EXSTYLE</tt></td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t</td>
<td><tt>GWL_</tt> int32_t</td>
</tr>
<tr>
<td><tt>USERDATA</tt></td>
<td>&nbsp;</td>
<td><tt>GWL_</tt> int32_t <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a5.png" alt="💥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
<td><tt>GWLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>MSGRESULT</tt></td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>DLGPROC</tt></td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWLP_</tt> intptr_t ◱</td>
</tr>
<tr>
<td><tt>USER</tt></td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWL_</tt> int32_t</td>
<td><tt>DWLP_</tt> intptr_t ◱</td>
</tr>
</tbody>
</table>
<p>From the prefix on the name of the extra bytes, you can read off which function it is meant to be used with.</p>
<table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Prefix</th>
<th>Function</th>
</tr>
<tr>
<td><tt>GCW_</tt> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <tt><u>G</u>et<u>C</u>lass<u>W</u>ord</tt></td>
<td><tt>GWW_</tt> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <tt><u>G</u>et<u>W</u>indow<u>W</u>ord</tt></td>
</tr>
<tr>
<td><tt>GCL_</tt> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <tt><u>G</u>et<u>C</u>lass<u>L</u>ong</tt></td>
<td><tt>GWL_</tt> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <tt><u>G</u>et<u>W</u>indow<u>L</u>ong</tt></td>
</tr>
<tr>
<td><tt>GCLP_</tt> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <tt><u>G</u>et<u>C</u>lass<u>L</u>ong<u>P</u>tr</tt></td>
<td><tt>GWLP_</tt> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <tt><u>G</u>et<u>W</u>indow<u>L</u>ong<u>P</u>tr</tt></td>
</tr>
</tbody>
</table>
<p>The weirdo is <tt>DWLP_</tt> because it needs to encode both the type of window that it can be used with (D = dialog) as well as the function name it goes with (<code><u>W</u>indow<u>L</u>ong<u>P</u>tr</code>).</p>
<p>As a concession, Windows lets you pass <tt>GCL_</tt> and <tt>GWL_</tt> values to <code>Get­Class­Long­Ptr</code> and <code>Get­Window­Long­Ptr</code> (respectively) even though they are intended to be used with <code>Get­Class­Long</code> and <code>Get­Window­Long</code> (respectively). If you do that, you get the corresponding 32-bit value zero-extended if necessary to be the size of a pointer.² This is seen primarily in the case of <tt>GWL_ID</tt> because most people don&#8217;t use the full range of IDs, so if you&#8217;re willing to live within the 32-bit subset, you can just pretend that the values are not pointer-sized.³</p>
<p>&#8220;Why bother changing all the prefixes? Doesn&#8217;t that just create a lot of busy work for people porting from 32-bit code to 64-bit code?&#8221;</p>
<p>Yes, but it&#8217;s good busy work. The point is to force build breaks at places where you need to make fixes, because you have to call the function that accesses a pointer-sized integer rather than a 32-bit integer; otherwise you suffer from integer truncation bugs.</p>
<p>¹ This is a common prefixing convention for classic Win32. For example, the operation parameter to <code>Show­Window</code> is prefixed <code>SW_</code>; the flags to <code>Set­Window­Pos</code> are prefixed <code>SWP_</code>; and the relationship parameter for <code>Get­Window</code> is prefixed <code>GW_</code>.</p>
<p>² The use of the <tt>GWL_</tt> values with <code>Set­Window­Long­Ptr</code> is a bit more problematic. It looks like you&#8217;re storing a pointer-sized integer, but only the bottom 32 bits are honored.</p>
<p>³ The <tt>ID</tt> is unusual in that it is defined both as <tt>GWL_ID</tt> and <tt>GWLP_ID</tt>. All of the other values are defined with only one prefix.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260629-00/?p=112484">The evolution of window and class extra bytes in Windows</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260629-00/?p=112484/feed</wfw:commentRss>
    <slash:comments>2</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>The case of the DLL that was not present in memory despite not being formally unloaded, part 2</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260626-00/?p=112472</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260626-00/?p=112472#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Fri, 26 Jun 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Code]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112472</guid>
    <content:encoded><![CDATA[<p>Last time, <a title="The case of the DLL that was not present in memory despite not being formally unloaded" href="https://devblogs.microsoft.com/oldnewthing/20260625-00/?p=112467"> we looked at crashes caused by a DLL being removed from memory behind everybody&#8217;s back</a>, causing crashes when somebody tried to call into that no-longer-there DLL that everybody thought was still there.</p>
<p>A colleague of mine who was looking at other crashes coming from this process found that most of those other crashes were also of the form &#8220;a data structure was corrupted because somebody wrote the single byte <tt>01</tt> into it.&#8221; That piece of information made everything fall into place for my side of the investigation.</p>
<p>We saw earlier that <a title="What does it mean when the bottom bit of my HMODULE is set?" href="https://devblogs.microsoft.com/oldnewthing/20260619-00/?p=112447"> the bottom bit of the <code>HMODULE</code> is set for datafile module handles</a>. Therefore, if one of these stray <tt>01</tt> bytes happens to overwrite the bottom byte of an existing <code>HMODULE</code> handle, that turns it into a (fake) datafile module handle. And then, during process destruction, a component dutifully cleans up the DLLs they loaded by freeing them (say because they were stored in an RAII type like <code>wil::<wbr />unique_<wbr />hmodule</code>), the code will pass this (fake) datafile module handle to <code>Free­Library</code>. The <code>Free­Library</code> function sees the bottom bit set and says, &#8220;Oh, this must be the handle to a module that was loaded via <code>LOAD_<wbr />LIBRARY_<wbr />AS_<wbr />DATAFILE</code>,&#8221; so it frees it as a datafile.</p>
<p>Freeing a datafile module means undoing the steps that were taken when the module was loaded as a datafile: Unmapping the DLL from memory. In particular, loading a module as a datafile does not add the DLL to the list of DLLs that were loaded as code; therefore, unloading a datafile module doesn&#8217;t remove it from that list. As far as the DLL list is concerned, the DLL is still in memory.</p>
<p>A one-bit error caused the code to lie and attempt to free a module handle that did not correspond to a <code>Load­Library</code> call, resulting in mass havoc.</p>
<p>The &#8220;DLL unmapped from memory&#8221; crash is just an alternate manifestation of the &#8220;somebody is writing <tt>01</tt> bytes to places they shouldn&#8217;t&#8221; bug. The original bug had a larger <a title="Microspeak: Bucket bugs, bucket spray, bug spray, and failure shift" href="https://devblogs.microsoft.com/oldnewthing/20200121-00/?p=103351"> bucket spray</a> than we initially thought.</p>
<p>The good news is that all of the crashes have funneled down to a single bug. The bad news is that you now have to debug this one memory corruption bug.</p>
<p>Unfortunately, at the time of this writing, the root memory corruption bug in the third party program has yet to be identified. We don&#8217;t know whether it&#8217;s coming from an operating system component or from the program itself. Though the fact that it appears to occur only in one process, where it sprays across multiple modules, suggests that it&#8217;s a problem with that program, or that there&#8217;s something peculiar about how this specific process uses the system.</p>
<p>If you look at the original stack trace, you can see that the problem is occurring at process termination. That&#8217;s probably why the problem has lurked for so long: Crashes at exit often go unnoticed because there is no end-user loss of functionality. The user was finished with the program anyway. Whether it exits cleanly or with a crash doesn&#8217;t affect the user much.</p>
<p>Sorry. Not all stories have a happy ending.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260626-00/?p=112472">The case of the DLL that was not present in memory despite not being formally unloaded, part 2</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260626-00/?p=112472/feed</wfw:commentRss>
    <slash:comments>21</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>Raymond&#8217;s hot take on Hainanese chicken</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260625-01/?p=112469</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260625-01/?p=112469#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Thu, 25 Jun 2026 14:00:01 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Non-Computer]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112469</guid>
    <content:encoded><![CDATA[<p>&#8220;If we give it no flavor, we can say that it&#8217;s really subtle.&#8221;</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260625-01/?p=112469">Raymond&#8217;s hot take on Hainanese chicken</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260625-01/?p=112469/feed</wfw:commentRss>
    <slash:comments>3</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>The case of the DLL that was not present in memory despite not being formally unloaded, part 1</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260625-00/?p=112467</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260625-00/?p=112467#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Thu, 25 Jun 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Code]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112467</guid>
    <content:encoded><![CDATA[<p>The team responsible for shell32.dll received a bug saying that they were responsible for a large number of crashes in a particular third party program. Opening the crash dumps showed the clear signs of a stack overflow:</p>
<pre><span style="border: solid 1px transparent;"> # Child-SP          RetAddr           Call Site</span>
<span style="border: solid 1px transparent;">00 000000ba`92851098 00007ff9`fed521c1 ntdll!_chkstk+0x37</span>
<span style="border: solid 1px transparent;">01 000000ba`928510b0 00007ff9`feea5ace ntdll!RtlDispatchException+0x2d1</span>
<span style="border: solid 1px transparent;">02 000000ba`92851300 00007ff9`fed4e02d ntdll!KiUserExceptionDispatch+0x2e</span>
<span style="border: solid 1px transparent;">03 000000ba`92852060 00007ff9`fed5222f ntdll!RtlLookupFunctionEntry+0x8d</span>
<span style="border: solid 1px transparent;">04 000000ba`928520b0 00007ff9`feea5ace ntdll!RtlDispatchException+0x33f</span>
<span style="border: solid 1px transparent;">05 000000ba`92852800 00007ff9`fed4e02d ntdll!KiUserExceptionDispatch+0x2e</span>
<span style="border: solid 1px transparent;">06 000000ba`92853560 00007ff9`fed5222f ntdll!RtlLookupFunctionEntry+0x8d</span>
<span style="border: solid 1px transparent;">07 000000ba`928535b0 00007ff9`feea5ace ntdll!RtlDispatchException+0x33f</span>
<span style="border: solid 1px transparent;">08 000000ba`92853d00 00007ff9`fed4e02d ntdll!KiUserExceptionDispatch+0x2e</span>
<span style="border: solid 1px currentcolor; border-bottom: none;">09 000000ba`92854a60 00007ff9`fed5222f ntdll!RtlLookupFunctionEntry+0x8d </span>
<span style="border: 1px currentcolor; border-style: none solid;">0a 000000ba`92854ab0 00007ff9`feea5ace ntdll!RtlDispatchException+0x33f  </span>
<span style="border: solid 1px currentcolor; border-top: none;">0b 000000ba`92855200 00007ff9`fed51f29 ntdll!KiUserExceptionDispatch+0x2e</span>
<span style="border: solid 1px transparent;">0c 000000ba`92855f70 00007ff9`feea5ace ntdll!RtlLookupFunctionEntry+0x8d</span>
<span style="border: solid 1px transparent;">0d 000000ba`928561c0 00007ff9`fed4e02d ntdll!RtlDispatchException+0x33f
<span style="border: solid 1px transparent;">...</span>
</span></pre>
<p>The highlighted block of stack frames (from <code>Rtl­Lookup­Function­Entry</code> to <code>Ki­User­Exception­Dispatch</code>) repeated for a very long time.</p>
<p>We are clearly in some sort of recursive exception handling death spiral. An exception occurred, and the kernel has decided that it is not something that kernel mode can handle,¹ so it reflected the exception back into user mode for further processing (<code>Ki­User­Exception­Dispatch</code>). While trying to figure out which exception handler to call, (<code>Rtl­Lookup­Function­Entry</code>), we took an exception, which restarted the exception loop.</p>
<p>Eventually, all of these recursive exceptions exhausted the stack, and we take a stack overflow exception that terminates the process.</p>
<p>The bug was assigned to shell32 because it looked like shell32 was the source of the original exception. If you walk all the way back to the bottom of the stack, you get something like this:</p>
<pre>23f 000000ba`9294c620 00007ff9`fed5222f ntdll!RtlLookupFunctionEntry+0x8d
240 000000ba`9294c670 00007ff9`feea5ace ntdll!RtlDispatchException+0x33f
241 000000ba`9294cdc0 00007ff9`fed4e02d ntdll!KiUserExceptionDispatch+0x2e
242 000000ba`9294db20 00007ff9`fed5222f ntdll!RtlLookupFunctionEntry+0x8d
243 000000ba`9294db70 00007ff9`feea5ace ntdll!RtlDispatchException+0x33f
244 000000ba`9294e2c0 00007ff9`fcba0af0 ntdll!KiUserExceptionDispatch+0x2e
245 000000ba`9294f018 00007ff9`fde2ad13 combase!CoTaskMemFree
246 000000ba`9294f020 00007ff9`fc7abc75 shell32!wil::details::string_maker::~string_maker+0x13
247 000000ba`9294f050 00007ff9`fc7ab897 ucrtbase!&lt;lambda_f03950bc5685219e0bcd2087efbe011e&gt;::operator()+0xa5
248 000000ba`9294f0a0 00007ff9`fc7ab84d ucrtbase!__crt_seh_guarded_call&lt;int&gt;::operator()+0x3b
249 000000ba`9294f0d0 00007ff9`fc7d2f0c ucrtbase!execute_onexit_table+0x3d
24a 000000ba`9294f110 00007ff9`fdff4645 ucrtbase!__crt_state_management::wrapped_invoke+0x2c
24b 000000ba`9294f140 00007ff9`fdff476e shell32!dllmain_crt_process_detach+0x45
24c 000000ba`9294f180 00007ff9`fee9f6fe shell32!dllmain_dispatch+0xe6
24d 000000ba`9294f1e0 00007ff9`fed4bcae ntdll!LdrpCallInitRoutineInternal+0x22
24e 000000ba`9294f210 00007ff9`fedcd37f ntdll!LdrpCallInitRoutine+0x10e
24f 000000ba`9294f280 00007ff9`fedcc54e ntdll!LdrShutdownProcess+0x17f
250 000000ba`9294f390 00007ff9`fdcb18ab ntdll!RtlExitUserProcess+0x9e
251 000000ba`9294f3c0 00007ff9`e754882e kernel32!ExitProcessImplementation+0xb
252 000000ba`9294f3f0 00007ff9`e754f344 mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes+0x2fa
253 000000ba`9294f6d0 00007ff9`e66f464b mscoreei!CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit+0x14
254 000000ba`9294f700 00007ff9`e66f44c9 clr!EEPolicy::ExitProcessViaShim+0x8b
255 000000ba`9294f760 00007ff9`e66f441e clr!SafeExitProcess+0x9d
256 000000ba`9294f9e0 00007ff9`e66f3f44 clr!HandleExitProcessHelper+0x3e
257 000000ba`9294fa10 00007ff9`e66f3e24 clr!_CorExeMainInternal+0xf8
258 000000ba`9294faa0 00007ff9`e753d6da clr!CorExeMain+0x14
259 000000ba`9294fae0 00007ff9`e75d785b mscoreei!CorExeMain+0xfa
25a 000000ba`9294fb40 00007ff9`fdc9e8d7 mscoree!CorExeMain_Exported+0xb
25b 000000ba`9294fb70 00007ff9`fedcc40c kernel32!BaseThreadInitThunk+0x17
25c 000000ba`9294fba0 00000000`00000000 ntdll!RtlUserThreadStart+0x2c
</pre>
<p>The repeating block stops at the source of the first exception: <code>combase!<wbr />Co­Task­Mem­Free</code>.</p>
<p>We can look for the exception record to see what the original problem was.</p>
<p>The exception record and context record are probably passed to <code>Rtl­Dispatch­Exception</code>, so we can see what <code>Ki­User­Exception­Dispatch</code> passes.</p>
<pre>  # Child-SP          <span style="border: solid 1px transparent;">RetAddr</span>           Call Site
243 000000ba`9294db70 <span style="border: solid 1px currentcolor;">00007ff9`feea5ace</span> ntdll!RtlDispatchException+0x33f
244 000000ba`9294e2c0 <span style="border: solid 1px transparent;">00007ff9`fcba0af0</span> ntdll!KiUserExceptionDispatch+0x2e

0:000&gt; u ntdll!KiUserExceptionDispatch 00007ff9`feea5ace 
ntdll!KiUserExceptionDispatch:
00007ff9`feea5aa0 cld
00007ff9`feea5aa1 mov     rax,qword ptr [ntdll!Wow64PrepareForException (00007ff9`fef272f0)]
00007ff9`feea5aa8 test    rax,rax
00007ff9`feea5aab je      ntdll!KiUserExceptionDispatch+0x1c (00007ff9`feea5abc)
00007ff9`feea5aad mov     rcx,rsp
00007ff9`feea5ab0 add     rcx,4F0h
00007ff9`feea5ab7 mov     rdx,rsp
00007ff9`feea5aba call    rax
00007ff9`feea5abc <span style="border: solid 1px currentcolor; border-bottom: none;">mov     rcx,rsp </span>
00007ff9`feea5abf <span style="border: 1px currentcolor; border-style: none solid;">add     rcx,4F0h</span>
00007ff9`feea5ac6 <span style="border: solid 1px currentcolor; border-top: none;">mov     rdx,rsp </span>
00007ff9`feea5ac9 call    ntdll!RtlDispatchException (00007ff9`fed51ef0)
00007ff9`feea5ace test    al,al
</pre>
<p>We see that the two parameters passed to <code>Rtl­Dispatch­Exception</code> are at <code>rsp+4f0h</code> and <code>rsp</code>. I&#8217;m guessing that the exception record comes first, followed by the context record, since that&#8217;s the order that those pointers appear in the <code>EXCEPTION_POINTERS</code>.</p>
<pre>  # <span style="border: solid 1px transparent;">Child-SP</span>          RetAddr           Call Site
244 <span style="border: solid 1px currentcolor;">000000ba`9294e2c0</span> 00007ff9`fcba0af0 ntdll!KiUserExceptionDispatch+0x2e

00007ff9`feea5ace test    al,al
0:000&gt; dps 000000ba`9294e2c0+4f0
000000ba`9294e7b0  00000000`<span style="border: solid 1px currentcolor;">c0000005</span> ← STATUS_ACCESS_VIOLATION
000000ba`9294e7b8  00000000`00000000
000000ba`9294e7c0  00007ff9`fcba0af0 combase!CoTaskMemFree
000000ba`9294e7c8  00000000`00000002
000000ba`9294e7d0  00000000`00000008
</pre>
<p>Yup, that looks like an exception record. It starts with the exception code, and is shortly after followed by the code address where the exception was taken.</p>
<pre>0:000&gt; .exr 000000ba`9294e2c0+4f0
ExceptionAddress: 00007ff9fcba0af0 (combase!CoTaskMemFree)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000008
   Parameter[1]: 00007ff9fcba0af0
Attempt to execute non-executable address 00007ff9fcba0af0
</pre>
<p>Okay, so we attempted to execute a non-executable address, and the address is <code>combase!<wbr />Co­Task­Mem­Free</code>.</p>
<p>Just for fun, let&#8217;s confirm that the second parameter really is a context record:</p>
<pre>0:000&gt; .cxr 000000ba`9294e2c0
rax=00007ff9fe3a9850 rbx=000001bbebd12388 rcx=000001bbebd63140
rdx=00007ff9fe4e99e0 rsi=000001bbebd12828 rdi=000001bbebd12310
rip=00007ff9fcba0af0 rsp=000000ba9294f018 rbp=0000df1c60b20569
 r8=000001bbebd12310  r9=0000df1c60b20569 r10=d94b3944a87271f0
r11=000000000000000b r12=0000000000000001 r13=00007ff9fdff47c0
r14=000000ba9294f128 r15=000001bbebd12310
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
combase!CoTaskMemFree:
00007ff9`fcba0af0 sub     rsp,28h
</pre>
<p>Yup, looks like a context record.</p>
<p>But wait, the exception claims that <code>combase!<wbr />Co­Task­Mem­Free</code> isn&#8217;t executable. First, let&#8217;s see if the debugger agrees with this assessment.</p>
<pre>0:000&gt; !address  00007ff9`fcba0af0

Usage:                  Image
Base Address:           00007ff9`fcb20000
End Address:            00007ff9`fcea6000
Region Size:            00000000`00386000 (   3.523 MB)
State:                  00010000          <span style="border: solid 1px currentcolor;">MEM_FREE</span>
Protect:                00000001          <span style="border: solid 1px currentcolor;">PAGE_NOACCESS</span>
Type:                   &lt;info not present at the target&gt;
Image Path:             C:\Windows\System32\combase.dll
Module Name:            combase
Loaded Image Name:      combase.dll
Mapped Image Name:      C:\symbols\combase.dll
More info:              lmv m combase
More info:              !lmi combase
More info:              ln 0x7ff9fcba0af0
More info:              !dh 0x7ff9fcb20000

Content source: 2 (mapped), length: 1eb510
</pre>
<p>The memory that contains the <code>Co­Task­Mem­Free</code> function has been freed!</p>
<p>In fact, if you look at the base address and region size, you see that the entirety of <tt>combase.dll</tt> has been unloaded from memory.</p>
<p>On the other hand, if you ask the loader what it thinks about that address, it says &#8220;Oh, that&#8217;s code inside <tt>combase.dll</tt>.&#8221;</p>
<pre>0:000&gt; !dlls -c 00007ff9`fcba0af0

0x1bbeb111020: C:\WINDOWS\System32\combase.dll
      Base   0x7ff9fcb20000  EntryPoint  0x7ff9fcc9a9d0  Size        0x00386000    DdagNode     0x1bbeb114380
      Flags  0x0028a2cc  TlsIndex    0x00000000  LoadCount   0xffffffff    NodeRefCount 0x00000000
             &lt;unknown&gt;
             LDRP_LOAD_NOTIFICATIONS_SENT
             LDRP_IMAGE_DLL
             LDRP_PROCESS_ATTACH_CALLED
</pre>
<p>Okay, now that we&#8217;ve gathered evidence, let&#8217;s see what theory we can develop.</p>
<p>The <tt>combase.dll</tt> is still in the loader&#8217;s bookkeeping, and we see that its load count is <tt>0xFFFFFFFF</tt>, which means that the DLL has been &#8220;pinned&#8221;, meaning that the loader will never unload it. These two pieces of information suggest that the DLL was not removed from memory by <code>Free­Library</code>, but rather by somebody explicitly freeing it, say by doing a <code>Virtual­Free</code> on the memory.</p>
<p>My guess is that a memory corruption bug somewhere caused some code to clean up the wrong memory blocks, and it unwittingly freed the memory occupied by <tt>combase.dll</tt>, say because somebody overwrote its &#8220;don&#8217;t forget to free this&#8221; variable with the address of <tt>combase.dll</tt>, or because there is an uninitialized variable bug, and the uninitialized value just happened to be a leftover copy of <tt>combase.dll</tt>&#8216;s base address.</p>
<p>But either way, the problem is not with shell32. Shell32 is just another victim, being the first DLL to call into combase after it was forcibly removed from memory by some unknown component.</p>
<p>If this theory is true, then I should be able to find similar types of crashes where some other DLL is the victim of a DLL being forcibly removed from memory.</p>
<p>I asked for the 100 most recent crashes in that third party program and put them into a pivot table so I could see the distribution.</p>
<table class="cp3" style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th>Failure type</th>
<th>Count</th>
</tr>
<tr>
<td>bugcheck_0x124_0_&#8230;</td>
<td align="right">1</td>
</tr>
<tr>
<td>bugcheck_0x139_a_&#8230;</td>
<td align="right">1</td>
</tr>
<tr>
<td>bugcheck_0x7f_8_&#8230;</td>
<td align="right">1</td>
</tr>
<tr>
<td>bugcheck_0xe6_26_&#8230;</td>
<td align="right">7</td>
</tr>
<tr>
<td>access_violation_c0000005_contoso!unknown_error_in_application</td>
<td align="right">23</td>
</tr>
<tr>
<td>access_violation_c0000005_gdi32full.dll!__dyn_tls_init</td>
<td align="right">1</td>
</tr>
<tr>
<td>access_violation_c0000005_shell32.dll!invokeshellexecutehook</td>
<td align="right">1</td>
</tr>
<tr>
<td>clr_exception_80004005_contoso!contoso.program.main</td>
<td align="right">3</td>
</tr>
<tr>
<td>clr_exception_80004005_contoso!unknown_function</td>
<td align="right">1</td>
</tr>
<tr>
<td>clr_exception_80070002_contoso!contoso.program.main</td>
<td align="right">6</td>
</tr>
<tr>
<td>clr_exception_80070002_contoso!unknown_function</td>
<td align="right">3</td>
</tr>
<tr>
<td>clr_exception_80070005_contoso!contoso.program.main</td>
<td align="right">1</td>
</tr>
<tr>
<td>clr_exception_8007000b_contoso!contoso.program.main</td>
<td align="right">21</td>
</tr>
<tr>
<td>clr_exception_8007000e_contoso!contoso.program.main</td>
<td align="right">1</td>
</tr>
<tr>
<td>clr_exception_80070422_windows.management.winmd!unknown</td>
<td align="right">1</td>
</tr>
<tr>
<td>clr_exception_800705af_contoso!contoso.program.main</td>
<td align="right">1</td>
</tr>
<tr>
<td>clr_exception_800705af_contoso!unknown_function</td>
<td align="right">2</td>
</tr>
<tr>
<td>clr_exception_8013152d_contoso!contoso.program.main</td>
<td align="right">1</td>
</tr>
<tr>
<td>illegal_instruction_c000001d_contoso!unknown_error_in_application</td>
<td align="right">1</td>
</tr>
<tr>
<td>stack_overflow_c0000005_contoso!unknown_error_in_application</td>
<td align="right">9</td>
</tr>
<tr>
<td>stack_overflow_c0000005_ctxapclient64.dll!unknown</td>
<td align="right">2</td>
</tr>
<tr>
<td>stack_overflow_c0000005_shell32.dll!wil::details::string_maker::~string_maker</td>
<td align="right">11</td>
</tr>
<tr>
<td>stack_overflow_c00000fd_contoso!unknown_error_in_process</td>
<td align="right">1</td>
</tr>
</tbody>
</table>
<p>The shell32 bug is the second-from the bottom, responsible for 11% of the crashes. But there are 13 other stack overflow bugs. And there are also a bunch of access violations in &#8220;unknown&#8221;.</p>
<p>I spot checked those stack overflow and &#8220;unknown access violation&#8221; crashes, and I found that they were all the same form as the shell32 bug, but with different DLLs: While sending <code>DLL_<wbr />PROCESS_<wbr />DETACH</code> notifications, a DLL was found to have been forcible removed from memory, and whatever DLL was the next one to call into that force-unloaded DLL was blamed, even though it was the victim. (A bunch of these arrived as &#8220;unknown access violation&#8221; because the system saw the crash inside the exception dispatching code and was for some reason unable to walk the stack all the way to the start of the recursive crash loop.)</p>
<p>So a total of 46% of the crashes were due to this rogue force-unload of a DLL. This is a case of <a title="Microspeak: Bucket bugs, bucket spray, bug spray, and failure shift" href="https://devblogs.microsoft.com/oldnewthing/20200121-00/?p=103351"> bucket spray</a>, where a single underlying cause generates a large number of different types of crashes.</p>
<p>The good news for the shell32 team is that they are off the hook; they are the victim. The bad news is that we don&#8217;t know who the culprit is.</p>
<p>Next time, we&#8217;ll learn some more about these crashes, and that will help confirm some theories about this specific one and may even discredit other theories.</p>
<p>¹ Things that kernel mode can handle are things like guard page exceptions (by expanding the stack) or page faults in paged-out memory (by paging it back in).</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260625-00/?p=112467">The case of the DLL that was not present in memory despite not being formally unloaded, part 1</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260625-00/?p=112467/feed</wfw:commentRss>
    <slash:comments>5</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>Cancellation of Windows Runtime activities is asynchronous</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260624-00/?p=112465</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260624-00/?p=112465#respond</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Wed, 24 Jun 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Code]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112465</guid>
    <content:encoded><![CDATA[<p>In the Windows Runtime, there are four interface patterns for representing asynchronous activity.</p>
<table style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3">
<tbody>
<tr>
<th> </th>
<th>No return type</th>
<th>With return type <tt>T</tt></th>
</tr>
<tr>
<th>Without progress</th>
<td><tt>IAsyncAction</tt></td>
<td><tt>IAsyncOperation&lt;T&gt;</tt></td>
</tr>
<tr>
<th>With progress</th>
<td><tt>IAsyncActionWithProgress&lt;P&gt;</tt></td>
<td><tt>IAsyncOperationWithProgress&lt;T, P&gt;</tt></td>
</tr>
</tbody>
</table>
<p>For the purpose of this discussion, I will collectively call these &#8220;asynchronous activities&#8221;.</p>
<p>One of the things you can do with asynchronous activities is cancel them, by calling the <code>Cancel</code> method. This method submits a request to cancel, but it does not wait for the operation to acknowledge the cancellation. If you want to wait for the operation to stop executing, you have to wait for it to call the completion callback.²</p>
<p>Asynchronous cancellation is important for avoiding deadlocks.</p>
<p>Most of the time, the scenarios involve cross-thread synchronous calls, but here&#8217;s an extremely obvious way it can happen.</p>
<p>Suppose that you have registered a progress callback on your asynchronous activity with progress.</p>
<pre>// C#
async Task DoSomethingWithTimeoutAsync()
{
    var op = DoSomethingAsync();
    op.Progress = (sender, p) =&gt; {
        UpdateProgress(p);
        if (p &gt;= 0.5) {
            sender.Cancel();
        }
    };
    try {
        await op;
    } catch (TaskCanceledException) {
        // ignore cancellation
    }
}

// C++/WinRT
winrt::fire_and_forget Widget::DoSomethingWithTimeoutAsync()
{
    auto op = DoSomethingAsync();
    op.Progress([&amp;](auto&amp;&amp; sender, auto p) {
        this-&gt;UpdateProgress(p);
        if (p &gt;= 0.5) {
            sender.Cancel();
        }
    });

    try {
        co_await op;
    } catch (winrt::hresult_canceled const&amp;) {
        // ignore cancellation
    }
    co_return;
}
</pre>
<p>The code calls <code>DoSomethingAsync()</code> and attaches a progress callback which cancels the operation once the progress reaches 50%. If the <code>Cancel()</code> method waited for outstanding progress callbacks to completed, you have a deadlock: The <code>Cancel()</code> is waiting for the progress callback to complete. But the progress callback is itself calling <code>Cancel()</code>.¹</p>
<p>To avoid deadlocks when cancellation occurs while a progress callback is in progress, the cancellation method doesn&#8217;t wait for an acknowledgment. If you want to know when the activity is finished, wait for it to complete. If you want to ignore progress reports that arrive after you cancel, you can do that yourself.</p>
<pre>// C#

async Task DoSomethingWithTimeoutAsync()
{
    var op = DoSomethingAsync();
    <span style="border: solid 1px currentcolor;">bool canceled = false;</span>
    op.Progress = (sender, p) =&gt; {
        <span style="border: solid 1px currentcolor;">if (!canceled) {</span>
            UpdateProgress(p);
            if (p &gt;= 0.5) {
                <span style="border: solid 1px currentcolor;">canceled = true;</span>
                sender.Cancel();
            }
        }
    };
    try {
        await op;
    } catch (TaskCanceledException) {
        // ignore cancellation
    }
}

// C++/WinRT

winrt::fire_and_forget Widget::DoSomethingWithTimeoutAsync()
{
    auto op = DoSomethingAsync();
    <span style="border: solid 1px currentcolor;">bool canceled = false;</span>
    op.Progress([&amp;](auto&amp;&amp; sender, auto p) {
        <span style="border: solid 1px currentcolor;">if (!canceled) {</span>
            this-&gt;UpdateProgress(p);
            if (p &gt;= 0.5) {
                <span style="border: solid 1px currentcolor;">canceled = true;</span>
                sender.Cancel();
            }
        }
    });

    try {
        co_await op;
    } catch (winrt::hresult_canceled const&amp;) {
        // ignore cancellation
    }
    co_return;
}
</pre>
<p>(The <code>canceled</code> variable doesn&#8217;t need to be atomic because progress callbacks do not overlap.)</p>
<p>Notice in the C++/winRT version that even after we call <code>Cancel()</code>, we wait for the <code>co_await op</code> to report completion before we return. Otherwise, the <code>Progress</code> callback will access an already-destroyed <code>canceled</code> variable.</p>
<p>¹ This is also the cancellation model for <a title="Ready. cancel. wait for it! (part 1)" href="https://devblogs.microsoft.com/oldnewthing/20110202-00/?p=11613"> I/O</a> and <a title="Ready. cancel. wait for it! (part 3)" href="https://devblogs.microsoft.com/oldnewthing/20110204-00/?p=11583"> RPC</a>: The cancellation method submits a cancellation request and returns immediately, and the underlying operation indicates that it has stopped executing by reporting some sort of completion.</p>
<p>² You might try to solve this by saying &#8220;Cancellation is asynchronous if the <code>Cancel</code> is issued from the same thread as the progress event&#8221;, but that doesn&#8217;t help in this case, which is more realistic:</p>
<pre>// C#
async void CancelAfter(IAsyncInfo op, TimeSpan delay)
{
    co_await Task.Delay(delay);
    op.Cancel();
}

async Task DoSomethingWithTimeoutAsync()
{
    var op = DoSomethingAsync();
    op.Progress = (sender, p) =&gt; {
        Invoke(() =&gt; UpdateProgress(p));
    };
    CancelAfter(op, TimeSpan.FromSeconds(5));
    try {
        await op;
    } catch (TaskCanceledException) {
        // ignore cancellation
    }
}
</pre>
<p>Suppose the Progress event is raised on a background thread at 4.9999 seconds. Before the lambda can call <code>Invoke()</code>, the <code>Cancel­After­Delay</code> timeout elapses, and the UI thread calls <code>Cancel()</code>. Now you have a deadlock because the Progress event is waiting for the lambda, the lambda is waiting for the Invoke, the Invoke is waiting for the UI thread, the UI thread is waiting for the Cancel, and the Cancel is waiting for the Progress event.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260624-00/?p=112465">Cancellation of Windows Runtime activities is asynchronous</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260624-00/?p=112465/feed</wfw:commentRss>
    <slash:comments>0</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
		
  <item>
    <title>Microspeak elaborated: Isn&#8217;t escrow just a release candidate by another name?</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260623-00/?p=112462</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260623-00/?p=112462#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Tue, 23 Jun 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <category><![CDATA[Microspeak]]></category>
    <category><![CDATA[Other]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112462</guid>
    <content:encoded><![CDATA[<p>I had earlier introduced the Microspeak term <a title="Microspeak: Escrow" href="https://devblogs.microsoft.com/oldnewthing/20260217-00/?p=112067"> escrow</a> to refer to the declaration that a particular build of the product is going to be the one that ships to customers if it meets certain quality and reliability targets.</p>
<p>Some people wondered, &#8220;Isn&#8217;t that just a release candidate? Why do you Microsoft people have to make up new names for things that already have perfectly good names?&#8221;</p>
<p>Yes, the Microspeak term <i>escrow</i> corresponds to what most people call a <i>release candidate</i>, but we don&#8217;t call it a release candidate because that name is used for some other purpose.</p>
<p>I wrote about this quite some time ago, but it was for the now-defunct <i>TechNet Magazine</i>, not for the blog, which means that it doesn&#8217;t show up in a blog search.</p>
<p>Here&#8217;s the final draft of that column. Now that I&#8217;ve put it on the blog, people can find it more easily.</p>
<div style="border: solid 1px currentcolor; padding: 3pt 1em;">
<p>Back in the old days of Windows, prerelease versions of the product followed a fairly standard progression. First up were the alpha releases, which were used internally and possibly shared with software partners outside of the Windows product team. Actually, to be quite honest, I never remember them being called alpha releases—they just were just called something boring like internal prerelease or simply named after the build number or project milestone that produced them. For example, Windows 95 prereleases went by names such as Build 81 and M3.</p>
<p>After alpha releases, there naturally come beta releases, which were sent to a somewhat broader audience. One major difference between alpha and beta releases is that beta releases include people who aren&#8217;t software developers, such as end users who like testing prerelease software and corporations who want a head start on evaluating the new operating system to determine the compatibility of the new product not only with their critical in-house applications but also with their corporate network, standard hardware configurations, and system management tools.</p>
<p>Finally, you had release candidates. These were, as the name suggests, versions of the code that were candidates for final release. In other words, &#8220;If everything goes well, we&#8217;re shipping this puppy.&#8221; If some horrific bug was found that invalidated this expectation, then as soon as the bug was fixed, a new release candidate build was <a title="Microspeak: Spinning up or kicking off a build" href="https://devblogs.microsoft.com/oldnewthing/20131112-00/?p=2683"> spun up</a>, and the test cycle restarted. Windows 95 shipped its sixth release candidate.</p>
<p>I&#8217;m told that the Windows NT folks followed the same release naming pattern, but they ran into a problem: corporations didn&#8217;t bother testing their critical applications against beta releases of Windows NT. The logic generally went something like this: &#8220;Why bother? It&#8217;s just a beta. Betas are for fanboys. It&#8217;ll all be different in the final version anyway. Any testing we do now would just be a waste of time.&#8221; Similarly, software companies paid no attention to issues found during the beta testing of Windows NT. &#8220;We don&#8217;t support beta operating systems,&#8221; they would respond.</p>
<p>These companies would start testing in earnest once the actual release candidate builds came out. And they&#8217;d inevitable find a bunch of problems. Some were problems the companies could address on their own while other issues were more complex and had to do with Windows NT not being &#8220;compatible enough&#8221; with the previous version of the OS. Some problems were comparatively minor issues with the way a particular project feature worked, and some could be fixed in a short period of time. Meanwhile, other problems were so serious that the release management team agreed that it was necessary to delay the product&#8217;s release so the product team could resolve the problem.</p>
<p>These release candidate builds also generated a lot of suggestions. We received feedback such as, &#8220;we think the UI would look better if you arranged the buttons this way&#8221; and &#8220;rephrasing this message would be less confusing for our employees.&#8221; Those would have been great suggestions had they only arrived during the beta phase, but by the time the first release candidate is rolled out, it&#8217;s far too late to make changes to the visuals. The documentation and help files have already been written, the product has been translated into dozens of languages, and the screenshots for the manual and product box have already been laid out, tuned, color-separated, and printed. All that work isn&#8217;t going to be thrown out and redone just to move a button.</p>
<p>I recall a meeting during the Windows XP era when one of these last-minute changes was being debated. The proposed change would have required that a 20 kilobyte help file be altered so that the instructions corresponded to the new user interface design. The localization and translation representative (a woman who spoke English with a lovely French accent) informed us that re-translating the modified help file under the extremely tight time constraints would cost hundreds of thousands of dollars.</p>
<p>To counteract the prevailing attitude that betas don&#8217;t count, the Windows NT team resorted to grade inflation. There are still beta releases, but the late beta releases—when there is still time (but not much) to do some fine-tuning—became known as release candidates, and what used to be release candidates became known as escrow builds. The term escrow was a good choice in my opinion. It does a good job of conveying the sense of &#8220;It&#8217;s over. All that&#8217;s left to do is sign the papers. We&#8217;re not going to touch it unless there is a real emergency.&#8221;</p>
</div>
<p><b>Bonus chatter</b>: You can compare this submitted version against <a title="How Betas Became RCs" href="https://learn.microsoft.com/en-us/previous-versions/technet-magazine/cc194403(v=msdn.10)"> the version that was published</a> to see <a title="When you agree to write to a particular length, make sure your content is actually that length" href="https://devblogs.microsoft.com/oldnewthing/20090909-00/?p=16793"> what was trimmed to fit the page</a>. And a sign that this is an older document is its use of em-dashes, which are shunned nowadays due to their association with AI-generated text.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260623-00/?p=112462">Microspeak elaborated: Isn&#8217;t escrow just a release candidate by another name?</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
]]></content:encoded>
    <wfw:commentRss>https://devblogs.microsoft.com/oldnewthing/20260623-00/?p=112462/feed</wfw:commentRss>
    <slash:comments>10</slash:comments>
    <image type="image/png" url="https://devblogs.microsoft.com/oldnewthing/wp-content/uploads/sites/38/2025/10/banner-oldnewthing-blue.webp"/>
  </item>
	</channel>
</rss>
