<?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, 14 May 2026 18:42:29 +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>A constant-space linear-time algorithm for deleting all but the 10 most recent files in a directory</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260514-00/?p=112322</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260514-00/?p=112322#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Thu, 14 May 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=112322</guid>
    <content:encoded><![CDATA[<p>Say you have a directory full of files, and you want to delete all but the 10 most recent files. Is there a way to tell <code>Find­First­File</code> to enumerate the files in date order?</p>
<p>No, there is no way to tell <code>Find­First­File</code> to enumerate the files in date order. The files enumerated by <code>Find­First­File</code> are produced in whatever order the file system driver wants. For example, FAT typically enumerates them in the order the files appear in the directory listing, which could be in order of creation if the files were added sequentially, or some mishmash order if there were renames or deletions mixed in.</p>
<p>Since you can&#8217;t control the order in which the files are enumerated, you&#8217;ll have to do the sorting yourself. The naïve solution is to read in all the entries, sort them by last-modified date, and then delete all but the last ten. This is <var>O</var>(<var>n</var>) space and <var>O</var>(<var>n</var> log <var>n</var>) running time.</p>
<p>But you can do better.</p>
<p>This job calls for a priority queue. A priority queue is a data structure that supports these operations, where <var>n</var> is the number of items in the priority queue.</p>
<ul>
<li>Add sorted: <var>O</var>(log <var>n</var>)</li>
<li>Find largest: <var>O</var>(1)</li>
<li>Remove largest: <var>O</var>(log <var>n</var>)</li>
</ul>
<p>The above description is for a max-priority queue. There is also a min-priority queue where the final two operations are &#8220;find smallest&#8221; and &#8220;remove smallest&#8221;. The two versions are equivalent because you can just use a reverse-sense comparison to switch from one to the other.</p>
<p>What we can do is enumerate all the files and add them one by one to a min-priority queue sorted by modified date. The priority queue holds the newest items. If the priority queue size exceeds 10, then we delete the file corresponding to the &#8220;smallest&#8221; (earliest) entry in the priority queue, and the remove that entry from the priority queue.</p>
<p>Since the priority queue size has a fixed cap, all of the operations run in <var>O</var>(1) time because the value of <var>n</var> is bounded by a predetermined constant. (Of course, the larger the cap, the larger the constant in <var>O</var>(1).) The overall algorithm then runs in <var>O</var>(<var>n</var>) times, where <var>n</var> is the number of files in the directory.</p>
<p>Here&#8217;s a sketch of a solution. To get a min-priority heap, we have to reverse the sense of the comparison in <code>dateAscending</code>.</p>
<pre>constexpr int files_to_keep = 10;

auto dateAscending = [](const WIN32_FIND_DATA&amp; a, const WIN32_FIND_DATA&amp; b) {
    return CompareFileTime(&amp;a.ftLastWriteTime, &amp;b.ftLastWriteTime) &gt; 0;
    };

std::priority_queue&lt;WIN32_FIND_DATA,
        std::vector&lt;WIN32_FIND_DATA&gt;, decltype(dateAscending)&gt;
        names(dateAscending);

WIN32_FIND_DATA wfd;
wil::unique_hfind findHandle( FindFirstFileW(L"*.*", &amp;wfd));
if (findHandle.is_valid())
{
    do
    {
        if (wfd.dwFileAttributes &amp; FILE_ATTRIBUTE_DIRECTORY) {
            // Skip directories
            continue;
        }

        names.push(wfd);
        if (names.size() &gt; files_to_keep) {
            DeleteFileW(names.top().cFileName);
            names.pop();
        }
    } while (FindNextFileW(findHandle.get(), &amp;wfd));
}
</pre>
<p>It&#8217;s unfortunate that <code>std::<wbr />priority_<wbr />queue</code> doesn&#8217;t have a deduction guide that deduces the <code>Comparator</code>. We have to specify it explicitly, and since it comes after the <code>Container</code>, we have to write out the container type manually instead of allowing it to be deduced.</p>
<p>It&#8217;s also unfortunate that it&#8217;s hard to call <code>reserve()</code> on the vector hiding inside the <code>priority_<wbr />queue</code>. This means that the <code>names.push()</code> could throw an exception. At least we use an RAII type (<code>wil::<wbr />unique_<wbr />hfind</code>) to ensure that the find handle is not leaked.</p>
<p>If you have access to <code>std::<wbr />inplace_<wbr />vector</code>, you could use a</p>
<pre>std::priority_queue&lt;WIN32_FIND_DATA,
        std::inplace_vector&lt;WIN32_FIND_DATA, files_to_keep + 1&gt;,
        decltype(dateAscending)&gt; names(dateAscending);
</pre>
<p>to avoid memory allocations entirely. (It also makes it clearer that the algorithm is constant-space.)</p>
<p>This is an example of a so-called <a href="https://en.wikipedia.org/wiki/Online_algorithm">online algorithm</a>, an algorithm that does its work incrementally rather than requiring all of the input before it can start working.</p>
<p><b>Exercise</b>: What if the task was to delete the 10 oldest files?</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260514-00/?p=112322">A constant-space linear-time algorithm for deleting all but the 10 most recent files in a directory</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/20260514-00/?p=112322/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 hang when the user changed keyboard layouts</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260513-00/?p=112318</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260513-00/?p=112318#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Wed, 13 May 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=112318</guid>
    <content:encoded><![CDATA[<p>A customer reported that their program hung when the user changed keyboard layouts, say by using the <kbd>Win</kbd>+<kbd>Space</kbd> hotkey sequence. They debugged it as far as observing that the foreground window in their application received a <code>WM_<wbr />INPUT­LANG­CHANGE­REQUEST</code>, and when that message was passed to <code>Def­Window­Proc</code>, the call never returned. What&#8217;s so haunted about the <code>WM_<wbr />INPUT­LANG­CHANGE­REQUEST</code> message?</p>
<p>What&#8217;s so haunted about it is that the default behavior of the <code>WM_<wbr />INPUT­LANG­CHANGE­REQUEST</code> message is to change input language!</p>
<p>For historical (and therefore now compatibility) reasons, when a hotkey-initiated input language change request is accepted, the system applies the change to all threads of that process. This means that all UI threads of the process need to be pumping messages so that they can receive the notification that their keyboard state has changed.</p>
<p>In this case, the customer had a background thread that created a window but was not pumping messages. That prevented the language change from completing and caused the main UI thread to hang.</p>
<p>The customer wanted to know if there was a way to configure their program so that hotkey-initiated input language changes don&#8217;t require all threads to be pumping messages. But that&#8217;s trying to solve too narrow a problem. If your thread has created a window, then it must pump messages. Today it&#8217;s causing trouble with input language changes. Tomorrow, it&#8217;s going to cause problems with DDE, and the day after tomorrow, it&#8217;s going to cause problems with theme changes.</p>
<p>Even if you had a way to change the way language changes work, that&#8217;s just one of the problems that your non-responding thread is causing. You should fix the root cause: Either pump messages or destroy the window so that it is no longer a UI thread and is no longer obligated to pump messages.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260513-00/?p=112318">The case of the hang when the user changed keyboard layouts</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/20260513-00/?p=112318/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>Why do Windows client editions on 32-bit x86 systems artificially limit RAM to 4 GB?</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260512-00/?p=112316</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260512-00/?p=112316#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Tue, 12 May 2026 14:00:00 +0000</pubDate>
    <category><![CDATA[Old New Thing]]></category>
    <guid isPermaLink="false">https://devblogs.microsoft.com/oldnewthing/?p=112316</guid>
    <content:encoded><![CDATA[<p>Windows XP SP 2 introduced Data Execution Prevention (DEP), which takes advantage of a then-new feature of x86-class processors that allowed you to deny execution from data pages. The new feature was Physical Address Extensions (PAE) which also allowed those 32-bit processors to access physical RAM above the 4 GB boundary. Although you could turn on Data Execution Prevention on all systems, only server products would use the memory above 4 GB.</p>
<p>A reader asked, &#8220;What was the real reason client editions were prevented from using more than 4 GB of RAM?&#8221;</p>
<p>The use of the word &#8220;real&#8221; in the question implies that the reader believed that the official reason was a lie, and there was some nefarious evil reason for the limitation. It&#8217;s unclear what this nefarious reason would be. Maybe the reader thought the &#8220;real&#8221; reason was &#8220;To force users to buy copies of Windows Server, which is far more lucrative&#8221;, though that doesn&#8217;t make sense. The cheapest version of Windows Server 2003 32-bit edition that supported more than 4 GB of RAM was Enterprise Edition, which sold for $3,999.¹ This is an outrageous price for a consumer operating system.</p>
<p>The reason why consumer products don&#8217;t use RAM above 4 GB is explained <a title="Operating Systems and PAE Support" href="https://learn.microsoft.com/en-us/previous-versions/windows/hardware/design/dn613969(v=vs.85)"> in the documentation that accompanied the introduction of the feature</a> under &#8220;Driver issues&#8221;.</p>
<blockquote class="q">
<p>Typically, device drivers must be modified in a number of small ways. Although the actual code changes may be small, they can be difficult. This is because when not using PAE memory addressing, it is possible for a device driver to assume that physical addresses and 32-bit virtual address limits are identical. PAE memory makes this assumption untrue.</p>
<p>…</p>
<p>[M]any device drivers designed for these systems may not have been tested on system configurations with PAE enabled. In order to limit the impact to device driver compatibility, changes to the hardware abstraction layer (HAL) were made to Windows XP SP2 and Windows Server 2003 SP1 Standard Edition to limit physical address space to 4 GB.</p>
</blockquote>
<p>As explained above, memory above 4 GB was not enabled for compatibility reasons. Many drivers inadvertently assume that all physical address fit in 32 bits. (DMA drivers for example.) Those drivers would corrupt memory if memory above 4 GB were made available.</p>
<p>Memory above 4 GB is enabled on server because if you are a server administrator, you don&#8217;t install random drivers for that hand-held scanner you bought at Best Buy from the bargain bin for $10. Server administrators typically run only the plain vanilla drivers that come with Windows. (They don&#8217;t even install manufacturer video drivers.) All the drivers that come with Windows have been tested for addresses above 4 GB. That 2001 driver for the $10 handheld scanner has not, and there&#8217;s a good chance that it will truncate addresses above 4 GB and corrupt memory as a result.</p>
<p>The consumer market and the server market are very different in terms of usage pattern. Consumers will install practically anything. Server administrators install as little as possible. Consumers have no technical expertise. Server administrators have access to highly-skilled staff.</p>
<p>Of course, this is all now a historical oddity. Systems with only 4 GB of RAM are vanishingly rare, and Windows began discouraging the production of systems using 32-bit processors in 2020, finally ending the production of 32-bit editions entirely with Windows 11.</p>
<p>¹ The only other version that supported more than 4 GB of RAM was Datacenter Edition, and on the pricing sheet I found, they didn&#8217;t even bother listing the price. If you have to ask, you can&#8217;t afford it.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260512-00/?p=112316">Why do Windows client editions on 32-bit x86 systems artificially limit RAM to 4 GB?</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/20260512-00/?p=112316/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>Additional notes on controlling which handles are inherited by Create&#173;Process</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260511-00/?p=112313</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260511-00/?p=112313#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Mon, 11 May 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=112313</guid>
    <content:encoded><![CDATA[<p>Some time ago, I wrote about <a title="Programmatically controlling which handles are inherited by new processes in Win32" href="https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873"> programmatically controlling which handles are inherited by new processes in Win32</a> by using the <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />HANDLE_<wbr />LIST</code> to limit exactly which handles are inherited. That way, when you create a new process, you have precise control over which handles get inherited and don&#8217;t accidentally inherit handles created by unrelated components in your process.</p>
<p>A collegue of mine pointed out that you still have the reverse problem: Since handles must be marked as inheritable for them to participate in <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />HANDLE_<wbr />LIST</code>, if another thread calls <code>Create­Process</code> with <code>bInheritHandles</code> = <code>TRUE</code> but without using <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />HANDLE_<wbr />LIST</code>, then they will accidentally inherit all of <i>your</i> handles.</p>
<p>This problem could have been avoided if the <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />HANDLE_<wbr />LIST</code> allowed you to include non-inheritable handles, in which case they would be non-inheritable by normal <code>Create­Process</code> but inheritable if explicitly opted back in. But alas, that&#8217;s not how it was designed.</p>
<p>Instead, you can create a helper process. All this helper process does is wait for the main process to exit, and then exit itself.</p>
<pre>WaitForSingleObject(hMainProcess, INFINITE);
ExitProcess(0);
</pre>
<p>This process doesn&#8217;t sound like it&#8217;s doing anything useful, and it&#8217;s not. But what makes it useful is not what it&#8217;s doing but rather what is done <i>to</i> it.</p>
<p>The components in the main process create their handles as non-inheritable. When they wants to create a process with specific inherited handles, they duplicate the desired handles into the helper process (as inheritable), and then build a <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />HANDLE_<wbr />LIST</code> that lists those duplicates as handles to inherit. They also use the <code>PROC_<wbr />THREAD_<wbr />ATTRIBUTE_<wbr />PARENT_<wbr />PROCESS</code> to specify that the <i>helper</i> process is the parent process that the handles should be inherited from. Then they pass those thread attributes to <code>Create­Process</code>, and the new process will inherit exactly those handles. Then they clean up by closing the handles in the helper process with the help of <code>Duplicate­Handle</code> and <code>DUPLICATE_<wbr />CLOSE_<wbr />SOURCE</code>.</p>
<p>Notice that multiple threads can simultaneously be operating on the helper process in this way, so you need only one helper process to service all your handle-inheritance-control needs.</p>
<p>This avoids the accidental inheritance problem because the handles that belong to the components in the main process are still marked non-inheritable, so any other code in the main process that does a <code>Create­Process</code> will not inherit them.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260511-00/?p=112313">Additional notes on controlling which handles are inherited by &lt;CODE&gt;Create&shy;Process&lt;/CODE&gt;</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/20260511-00/?p=112313/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>Developing more confidence when tracking renames via Read&#173;Directory&#173;ChangesW</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260508-00/?p=112310</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260508-00/?p=112310#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Fri, 08 May 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=112310</guid>
    <content:encoded><![CDATA[<p>A customer was using <code>Read­Directory­ChangesW</code> to monitor the contents of a directory, and they were concerned about the <code>FILE_<wbr />ACTION_<wbr />RENAMED_<wbr />OLD_<wbr />FILE</code> and <code>FILE_<wbr />ACTION_<wbr />RENAMED_<wbr />NEW_<wbr />FILE</code> pair of actions. The documentation doesn&#8217;t guarantee that the two always occur consecutively, or even that they always appear in pairs. For peace of mind, the customer was looking for a way to match up each <code>FILE_<wbr />ACTION_<wbr />RENAMED_<wbr />OLD_<wbr />FILE</code> with a <code>FILE_<wbr />ACTION_<wbr />RENAMED_<wbr />NEW_<wbr />FILE</code> to make sure they were tracking the rename properly.</p>
<p>Yes, you can do it by switching from <code>Read­Directory­ChangesW</code>. to <code>Read­Directory­Changes­ExW</code> and asking for <code>Read­Directory­Notify­Extended­Information</code>. This produces the <code>FILE_<wbr />NOTIFY_<wbr />EXTENDED_<wbr />INFORMATION</code> structure, and that structure includes the <code>FileId</code> of the affected file. You can then match that up between the <code>FILE_<wbr />ACTION_<wbr />RENAMED_<wbr />OLD_<wbr />FILE</code> and <code>FILE_<wbr />ACTION_<wbr />RENAMED_<wbr />NEW_<wbr />FILE</code> to confirm that they are the two halves of the same rename operation.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260508-00/?p=112310">Developing more confidence when tracking renames via &lt;CODE&gt;Read&shy;Directory&shy;ChangesW&lt;/CODE&gt;</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/20260508-00/?p=112310/feed</wfw:commentRss>
    <slash:comments>7</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>When you upgrade your resource strings to Unicode, don&#8217;t forget to specify the L prefix</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260507-00/?p=112307</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260507-00/?p=112307#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Thu, 07 May 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=112307</guid>
    <content:encoded><![CDATA[<p>Some time ago, I discussed how <a title="The Resource Compiler defaults to CP_ACP, even in the face of subtle hints that the file is UTF-8" href="https://devblogs.microsoft.com/oldnewthing/20190607-00/?p=102569"> the Resource Compiler defaults to CP_ACP, even in the face of subtle hints that the file is UTF-8</a>.</p>
<p>After yet another incident of <a title="Making sure the Microsoft Visual C++ compiler chooses the right source encoding" href="https://devblogs.microsoft.com/oldnewthing/20241231-00/?p=110696"> Visual Studio secretly changing the file encoding from 1252 to UTF-8</a> and breaking all non-ASCII strings, combined with <a title="How various git diff viewers represent file encoding changes in pull requests" href="https://devblogs.microsoft.com/oldnewthing/20241230-00/?p=110692"> Azure DevOps and Visual Studio simply ignoring encoding changes when showing diffs</a>, a colleague decided to solve the problem once and for all by using explicit Unicode escapes <tt>\x####</tt> to represent non-ASCII characters. That way, it doesn&#8217;t matter whether the file encoding is 1252 or UTF-8 because the two code pages agree on the common ASCII subset.</p>
<p>What used to be</p>
<pre>IDS_AWESOME "That’s great!"
</pre>
<p>was changed to</p>
<pre>IDS_AWESOME "That\x2019s great!"
</pre>
<p>Unfortunately, the resulting string that appeared on screen was</p>
<blockquote class="q"><p>That 19s great!</p></blockquote>
<p>What went wrong?</p>
<p>If you are encoding Unicode into your string, you have to put an <code>L</code> prefix on the quoted string. Otherwise, the <tt>\xABCD</tt> sequence is interpreted as an 8-bit <tt>\xAB</tt> escape sequence, followed by two literal characters <tt>CD</tt>. In this case, the <tt>\x2019</tt> was interpreted as <tt>\x20</tt> (which encodes a space) followed by the literal characters <tt>19</tt>, resulting in the string <tt>That␣19s great!</tt>.</p>
<p>The correct conversion includes the <code>L</code> prefix.</p>
<pre>IDS_AWESOME <span style="border: solid 1px currentcolor;">L</span>"That\x2019s great!"
</pre>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260507-00/?p=112307">When you upgrade your resource strings to Unicode, don&#8217;t forget to specify the L prefix</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/20260507-00/?p=112307/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>Why not have changes in API behavior depend on the SDK you link against?</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260506-00/?p=112303</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260506-00/?p=112303#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Wed, 06 May 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=112303</guid>
    <content:encoded><![CDATA[<p>Some time ago, I noted that <a title="The Co­Initialize­Security function demands an absolute security descriptor" href="https://devblogs.microsoft.com/oldnewthing/20240902-00/?p=110201"> the <code>Co­Initialize­Security</code> function demands an absolute security descriptor</a>, even though many functions in Windows produce self-relative security descriptors, forcing you to perform a relative-to-absolute conversion, even though the function internally just converts it back from absolute to relative.</p>
<p><a href="https://devblogs.microsoft.com/oldnewthing/20240902-00/?p=110201&amp;commentid=141890#comment-141890"> Commenter tbodt wrote</a>,</p>
<blockquote class="q"><p>This one seems easy enough to fix by Apple&#8217;s technique of giving the function the old behavior when the program is linked against the old SDK.</p></blockquote>
<p>This sure sounds easy. If your program links with the newer SDK, then it gets the new behavior of accepting self-relative security descriptors. But if it links with the old SDK, then it gets the old behavior of requiring absolute security descriptors. If you want the new behavior, then you link with the new SDK.</p>
<p>This does create a subtlety that if you choose the wrong SDK to link against, everything still builds, but the results are different. Traditionally, Windows SDKs are forward-compatible: You can take an old program and link it against a newer SDK, and it will work exactly the same because the old program uses only the backward-compatible subset of the newer SDK. If you change behavior based on the SDK version that you link with, then it may not be obvious that the change in behavior you are experiencing is due to having upgraded the SDK libraries.</p>
<p>Also, what if a program is linked with one version of the SDK, but a DLL that it uses is linked with a different version of the SDK? Maybe you&#8217;re using a UI framework library that hasn&#8217;t seen any need to update to the newer SDK. Or maybe your program is the one using an old version of the SDK, but the UI framework library is using the newer one. Do you let the main program&#8217;s SDK version dictate the behavior of the function, even though the DLL is expecting different behavior? The poor DLL is going to call <code>Co­Initialize­Security</code>, and it won&#8217;t behave the way it expects.</p>
<p>Okay, so maybe you decide that the function changes its behavior not based on the program&#8217;s linked SDK version but rather the version of the calling DLL. But how does a function know which DLL called it? You might say, &#8220;Well, you can look at which DLL the return address belongs to.&#8221; But that doesn&#8217;t work in the case of tail call optimization.</p>
<pre>// some function in a DLL
HRESULT InitializeWidgets(
    UINT maxWidgets,
    const WIDGET_ID* ownerId,
    PCWSTR ownerDescription,
    PCWSTR countainerName,
    PCWSTR containerDescription,
    COLORREF defaultColor,
    UINT defaultWidth,
    UINT defaultHeight,
    bool isRemoteAccessible,
    bool isPersistent)
{
    ⟦ various initialization steps ⟧

    static BYTE sd[] = { 0x01, ⟦ hard-coded values ⟧ };

    return CoInitializeSecurity(sd, -1, nullptr, nullptr,
                                RPC_C_AUTHN_LEVEL_DEFAULT,
                                RPC_C_IMP_LEVEL_IDENTIFY,
                                nullptr, EOAC_NONE, nullptr);
}
</pre>
<p>That final call to <code>Co­Initialize­Security</code> could be optimized into a tail call, in which case the subroutine call instruction changes to an unconditional branch, with the return address being the address of <code>Initialize­Widget</code>&#8216;s caller. If <code>Co­Initialize­Security</code> snooped at its return address, it would be checking the SDK version of the wrong DLL.</p>
<p>Conversely, what if the function in the DLL is just a wrapper?</p>
<pre>HRESULT CoInitializeSecuritywithLogging(
    _In_opt_ PSECURITY_DESCRIPTOR pSecDesc,
    _In_ LONG cAuthSvc,
    _In_reads_opt_(cAuthSvc) SOLE_AUTHENTICATION_SERVICE* asAuthSvc,
    _In_opt_ void* pReserved1,
    _In_ DWORD dwAuthnLevel,
    _In_ DWORD dwImpLevel,
    _In_opt_ void* pAuthList,
    _In_ DWORD dwCapabilities,
    _In_opt_ void* pReserved3)
{
    if (dwCapabilities &amp; EOAC_APPID) {
        LogUuid("CoInitializeSecurity with APPID", (UUID*)pSecDesc);
    } else if (dwCapabilities &amp; EOAC_ACCESS_CONTROL) {
        Log("CoInitializeSecurity with IAccessControl");
    } else {
        LogSecurityDescriptor("CoInitializeSecurity with security descriptor", pSecDesc);
    }
    HRESULT hr = CoInitializeSecurity(pSecDesc, cAuthSvc, asAuthSvc, pReserved1,
                        dwAuthnLevel, dwImpLevel, pAuthList, dwCapabilities, pReserved3);
    Log("CoInitializeSecurity returned", hr);
}
</pre>
<p>If you look at the return address, you will find the wrapper function and change your behavior to match the version that the wrapper function was built with, but that wrapper function is just passing through the parameters from its caller. It&#8217;s really the caller whose behavior we want to match, not the wrapper.</p>
<p>And what if the library is a static library rather than a DLL? It was written for one version of the SDK, but you link to another, and the behavior changes, and even if the function checks the return address, it will get the DLL&#8217;s address and see the DLL&#8217;s SDK version rather than the version the library wanted.</p>
<p>Changing behavior based on the SDK version you link to works only if programs are monolithic.</p>
<p><b>Bonus chatter</b>: Changing to a newer SDK&#8217;s <i>header files</i> do create behavioral changes because, for example, structures with an explicit size member might get extended to contain additional fields, and the API uses the value of the size member to decide which version of the SDK the caller is using. But this is not dependent on the SDK that the caller links to, which is a good thing, because it lets you take static libraries which use different versions of the SDK header files and link them all together into a single program or DLL, and they will still work.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260506-00/?p=112303">Why not have changes in API behavior depend on the SDK you link against?</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/20260506-00/?p=112303/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>
		
  <item>
    <title>A dispute over the TAB key highlights a mismatch between Microsoft and IBM organizational structures</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260505-00/?p=112298</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260505-00/?p=112298#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Tue, 05 May 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=112298</guid>
    <content:encoded><![CDATA[<p><a title="The Microsoft/IBM joint development was built on mutual respect, wait, is respect the right word?" href="https://devblogs.microsoft.com/oldnewthing/20240827-00/?p=110186"> I&#8217;ve written in the past</a> about the cultural mismatch between Microsoft and IBM during the collaboration on OS/2, with the Microsofties viewing their IBM colleagues as <a title="Be careful with that thing, it's a confidential coffee maker" href="https://devblogs.microsoft.com/oldnewthing/20220426-00/?p=106528"> mired in pointless bureaucracy</a> and the IBM folks viewing Microsofties as undisciplined hackers.¹</p>
<p>One of many points of mismatch was the organizational structure.</p>
<p>A colleague recalls that while he was assigned to the IBM offices in Boca Raton, Florida, there was a dispute over what key should be used to move from one field to another in dialog boxes. The folks at IBM were not happy with my colleague&#8217;s decision to use the <kbd>TAB</kbd> key, so they asked him to escalate the issue to his manager back in Redmond.</p>
<p>My colleague&#8217;s manager replied, &#8220;The reason you are in Boca is to make these decisions so I don&#8217;t have to be in Boca.&#8221;</p>
<p>My colleague rephrased this reply in a more corporate manner before passing it on to IBM: &#8220;Microsoft supports the use of the <kbd>TAB</kbd> key for this purpose.&#8221;</p>
<p>Unsatisfied, the IBM folks escalated the issue up their organizational chain for several levels, and replied that their VP (who was around seven levels of management above the programmers) was absolutely opposed to the use of the <kbd>TAB</kbd> for this purpose, and they wanted confirmation from the equivalent-level manager at Microsoft that Microsoft stands by the choice of the <kbd>TAB</kbd> key.</p>
<p>My colleague replied, &#8220;Bill Gates&#8217;s mother is not interested in the <kbd>TAB</kbd> key.&#8221;</p>
<p>This apparently ended the discussion, and the <kbd>TAB</kbd> key stayed.</p>
<p><b>Note</b>: This upcoming Sunday is Mother&#8217;s Day in the United States. You probably shouldn&#8217;t ask her for her opinion on the <kbd>TAB</kbd> key.</p>
<p>¹ There was probably merit to both arguments.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260505-00/?p=112298">A dispute over the &lt;KBD&gt;TAB&lt;/KBD&gt; key highlights a mismatch between Microsoft and IBM organizational structures</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/20260505-00/?p=112298/feed</wfw:commentRss>
    <slash:comments>11</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>How do I inform Windows that I&#8217;m writing a binary file?</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260504-00/?p=112296</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260504-00/?p=112296#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Mon, 04 May 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=112296</guid>
    <content:encoded><![CDATA[<p>A customer wanted to know how to inform Windows that they were opening a file in text mode, as opposed to binary mode. That way, Windows can perform text conversions as necessary, like adding carriage returns before linefeeds, or converting ASCII to Unicode.</p>
<p>Windows doesn&#8217;t know whether your file is binary or text. As far as Windows is concerned, it&#8217;s just a bunch of bytes, and it&#8217;s up to you to interpret it. So in a sense, all files are binary files. If you want to insert carriage returns before linefeeds, you will have to do it yourself.</p>
<p>Now, it is often the case that you are using a higher level library, like the C runtime, in which case you can ask the library to do it for you, such as opening the file in <tt>"w"</tt> mode to indicate that the runtime should treat the file as a text file, or in <tt>"wb"</tt> to open as a binary file. But this work happens in the runtime library, not in Windows itself. The runtime library performs the necessary transformations and passes binary data to Windows. There are no further transformations once the data hits <code>Write­File</code>.</p>
<p>&#8220;But wait, there&#8217;s an old MS-DOS ioctl <a href="https://www.ctyme.com/intr/rb-2821.htm">AH=4401h (Set device information)</a> where you pass flags in DX, and <a href="https://www.ctyme.com/intr/rb-2820.htm"> bit 5 is the raw (binary) mode bit</a>. So what&#8217;s the Windows version of this ioctl?&#8221;</p>
<p>If you look more closely, that MS-DOS ioctl applies only to character devices. <a href="https://github.com/microsoft/MS-DOS/blob/2d04cacc5322951f187bb17e017c12920ac8ebe2/v2.0/source/XENIX2.ASM#L470"> If you try to use it on a disk file, you get <code>ERROR_<wbr />INVALID_<wbr />FUNCTION</code></a>.</p>
<pre>ioctl_check_permissions:
        CMP     AL,2
        JAE     ioctl_control_string
        CMP     AL,0
        MOV     AL,BYTE PTR ES:[DI+sf_fcb+fcb_devid]
        JZ      ioctl_read              ; read the byte
        OR      DH,DH
        JZ      ioctl_check_device      ; can I set with this data?
        error   error_invalid_data      ; no DH &lt;&gt; 0

ioctl_check_device:
        TEST    AL,devid_ISDEV          ; can I set this handle?
        <span style="border: solid 1px currentcolor;">JZ      ioctl_bad_fun           ; no, it is a file.</span>

...

ioctl_bad_fun:
        error   error_invalid_function
</pre>
<p>This IOCTL can be used to tell the console things like whether to perform line buffering on input. The Win32 equivalent is <code>Set­Console­Mode</code>, roughly corresponding to the Unix <tt>stty</tt>.</p>
<p>If you want to perform content transformations on files, you&#8217;ll have to do it yourself, or ask someone else (like the runtime library) to do it for you.</p>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260504-00/?p=112296">How do I inform Windows that I&#8217;m writing a binary file?</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/20260504-00/?p=112296/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>Developing a cross-process reader/writer lock with limited readers, part 4: Abandonment</title>
    <link>https://devblogs.microsoft.com/oldnewthing/20260501-00/?p=112291</link>
    <comments>https://devblogs.microsoft.com/oldnewthing/20260501-00/?p=112291#comments</comments>
    <dc:creator><![CDATA[Raymond Chen]]></dc:creator>
    <pubDate>Fri, 01 May 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=112291</guid>
    <content:encoded><![CDATA[<p>We&#8217;ve been building a cross-process reader/writer lock with a cap on the number of readers, we concluded our investigation last time by noting <a title="Developing a cross-process reader/writer lock with limited readers, part 3: Fairness" href="https://devblogs.microsoft.com/oldnewthing/20260430-00/?p=112288"> that there is a serious problem that needs to be fixed</a>.</p>
<p>That serious problem is abandonment.</p>
<p>Suppose a process crashes while it holds a shared or exclusive lock on our cross-process reader/writer lock. <a title="Semaphores don't have owners" href="https://devblogs.microsoft.com/oldnewthing/20051123-14/?p=33233"> Semaphores don&#8217;t have owners</a>, so if a thread terminates while in possession of a semaphore token, that token is lost forever. For our cross-process reader/writer lock, that means that the maximum number of shared acquirers goes down by one, and exclusive acquisitions will never succeed, since they will be waiting for that last token which will never be returned.</p>
<p>A synchronization object that does have the concept of ownership is the mutex, so we can build our reader/writer lock out of mutexes.</p>
<p>The idea here is that instead of claiming semaphore tokens, we claim mutexes. This means that we need one mutex for each potential shared acquisition, plus one more to avoid the starvation problem.</p>
<p>The outline is</p>
<ul>
<li>Shared acquisition: Claim any available token mutex.</li>
<li>Shared release: Release the claimed token mutex.</li>
<li>Exclusive acquisition: Claim all token mutexes.</li>
<li>Exclusive release: Release all token mutexes.</li>
</ul>
<pre>HANDLE sharedMutex;
HANDLE tokenMutexes[MAX_SHARED];

struct TimeoutTracker
{
    explicit TimeoutTracker(DWORD timeout)
        : m_timeout(timeout) {}

    DWORD m_start = GetTickCount();

    <span style="border: solid 1px currentcolor;">DWORD</span> Wait(HANDLE h)
    {
        DWORD elapsed = GetTickCount() - m_start;
        if (elapsed &gt; m_timeout) return <span style="border: solid 1px currentcolor;">WAIT_TIMEOUT</span>;
        return <span style="border: solid 1px currentcolor;">WaitForSingleObject(h, m_timeout - elapsed)</span>;
    }

    <span style="border: solid 1px currentcolor; border-bottom: none;">DWORD WaitMultiple(DWORD count, const HANDLE* handles, BOOL waitAll)            </span>
    <span style="border: 1px currentcolor; border-style: none solid;">{                                                                               </span>
    <span style="border: 1px currentcolor; border-style: none solid;">    DWORD elapsed = GetTickCount() - m_start;                                   </span>
    <span style="border: 1px currentcolor; border-style: none solid;">    if (elapsed &gt; m_timeout) return WAIT_TIMEOUT;                               </span>
    <span style="border: 1px currentcolor; border-style: none solid;">    return WaitForMultipleObjects(count, handles, waitAll, m_timeout - elapsed);</span>
    <span style="border: solid 1px currentcolor; border-top: none;">}                                                                               </span>
};
</pre>
<p>We change the return value of the <code>Wait</code> method so it returns the wait result rather than a success/failure. We also add a <code>Wait­Multiple</code> method for wrapping <code>Wait­For­Multiple­Objects</code>.</p>
<p>Next is a handy helper function.</p>
<pre>int WaitResultToindex(DWORD result)
{
    auto index = result - WAIT_OBJECT_0;
    if (index &lt; MAX_SHARED) return static_cast&lt;int&gt;(index);

    index = result - WAIT_ABANDONED_0;
    if (index &lt; MAX_SHARED) return static_cast&lt;int&gt;(index);

    return -1;
}
</pre>
<p>The <code>Wait­Result­To­Index</code> function takes the wait result and returns the index of the acquired mutex, or <tt>-1</tt> if no mutex was acquired.</p>
<p>Notice that this code treats the abandoned the state the same as the normal wait state. <!-- backref: What should I do if a wait call reports WAIT_ABANDONED? --> We are assuming that the code can recover from inconsistent data somehow. (For example, maybe the shared and exclusive accesses are to control access to a set of files, so the existing code already has to deal with file corruption.)</p>
<p>All that&#8217;s left is to implement the outline.</p>
<pre>int AcquireShared()
{
    WaitForSingleObject(sharedMutex, INFINITE);

    auto result = WaitForMultipleObjects(MAX_SHARED, tokenMutexes, FALSE /* bWaitAll */, INFINITE);

    ReleaseMutex(sharedMutex);

    return WaitResultToIndex(result);
}

void ReleaseShared(int index)
{
    ReleaseMutex(tokenMutexes[index]);
}

int AcquireSharedWithTimeout(DWORD timeout)
{
    TimeoutTracker tracker(timeout);
    DWORD result = tracker.Wait(hSharedMutex);
    if (result != WAIT_OBJECT_0) return -1;
    result = tracker.WaitMultiple(MAX_SHARED, tokenMutexes, FALSE /* waitAll */);
    ReleaseMutex(sharedMutex);

    return WaitResultToIndex(result);
}

void AcquireExclusive()
{
    WaitForSingleObject(sharedMutex, INFINITE);

    auto result = WaitForMultipleObjects(MAX_SHARED, tokenMutexes, TRUE /* bWaitAll */, INFINITE);

    ReleaseMutex(sharedMutex);
}

void ReleaseExclusive()
{
    for (unsigned i = 0; i &lt; MAX_SHARED; i++) {
        ReleaseMutex(tokenMutexes[i]);
    }
}

bool AcquireExclusiveWithTimeout(DWORD timeout)
{
    TimeoutTracker tracker(timeout);
    DWORD result = tracker.Wait(hSharedMutex);
    if (result != WAIT_OBJECT_0) return -1;
    result = tracker.WaitMultiple(MAX_SHARED, tokenMutexes, TRUE /* waitAll */);
    ReleaseMutex(sharedMutex);

    return result != WAIT_TIMEOUT;
}
</pre>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260501-00/?p=112291">Developing a cross-process reader/writer lock with limited readers, part 4: Abandonment</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/20260501-00/?p=112291/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>
	</channel>
</rss>
