Sunday, August 24, 2025

From the past: Win32s was both awesome and awful

 WIN32S: a programmer's dream from the Windows 3.1 era    

Gather 'round, young 'uns, and let me give you some wisdom from the days of the Win32s compatibility library. When Windows was being made, it was a 16-bit system, meaning that pointers were short and it was painful to address much memory. I'll also share a key feature of technical decisions: how to make an early decision that you don't know the ramifications of.

Quick aside: the people who made the 8086 and compilers as well as the PC and the OS knew this, so pointers were "actually" 20 bits (1 meg) thanks to the selectors and corresponding weird compiler switches, plus there was weird back-compat with the "A20" shim, and of course there was additional weirdness for some bank-selection.

Enter the RS/1 statistical program, which I helped port from its VAX/VMS and Unix heritage to the PC running Windows 3.1 and NT. It was a "big" program that had been in active development by a team of capable developers for ten years or more, with a ton of features. It had been designed originally to work on constrained computers like the PDP-11 (also a 16 bit machine with its own pointer weirdness).

A key early decision was to use the Win32s library on Windows 3.1. This library let us use 32-bit pointers in our code, which was a big plus. A downside, though, is that we didn't have any way to know about any potential downsides. Marketing material then, as now, talks big about how great new technology is, and doesn't much mention possible issues.

But there was an incredibly tiny flaw in the Win32s implementation of "unlink" that was to have enormous implications. To explain the bug, you have to know two things:

Firstly, the unlink call is used to "delete" a file. It's called unlink because technically the file isn't "deleted": it's merely removed from a directory. If the file is only present in one directory and no program is using it, the file is also deleted. 

Secondly, RS/1 kept track of every "table" of data as a separate file, and it used tables for pretty much everything. This was actually awesome, and I think more programming environments would benefit from a table-first approach. Some tables were permanent, but others were "temporary" and might be just in memory or might have a file backing depending on the available memory. Remember that RS/1 was designed for low-memory environments, so this automatically shuffling of data between disk and memory was a critical part of the program.

The bug

The bug in unlink was that if you did an unlink on a file that didn't exist, it returned the wrong value. Specifically, it returned success. The Posix standard was to return failure. This was critical to the underpinnings of RS/1: it's how the program knew if a temporary file was just in memory or was actually backed by a file on disk. By returning the wrong values, some internal bookkeeping got confused, and would eventually cause a crash.

This was caught, BTW, by the incredibly good $systemtest() function that RS/1 shipped with. Any user, at any time, could run $systemtest() and the program would do a pretty solid job of verifying that it was all running correctly.

On a Windows 3.1 machine, the program crashing also meant that the whole computer crashed, which was not ideal for debugging. I finally tracked it down by running RS/1 in a debugger on two machines, one running Windows 3.1 (where it used the Win32s library and crashed) and one running Windows NT (which used the native implementation which worked perfectly). I then started doing a sort of binary search to trace what was different about the two systems as it did the $systemtest().

Key takeaways for making early technical decisions

Bet on the future, not the past. We could have just made a 16-bit app. But the future was clearly 32-bit for Windows

Everything that isn't mainstream has bugs. Your schedule should include time for them. A constant in my technical career is that libraries that don't get much use have bugs, and there isn't much management resolve to fix them. In our case, the mainstream was either 16-bit code on Windows or 32-bit code on VMS or Unix. Win32s was a weird little library (as was the "PharLap" DOS extender that an earlier PC version used). 

Workarounds are better than hoping for a bugfix. If your library has an issue, it's best to figure out a workaround. Waiting for a fix that might never come will delay shipping, possibly forever. 


Thursday, August 21, 2025

Weird issues with WinUI3 and ALT key: so many beeps!

 Like any good developer, I want my WinUI3 app to have keyboard control over the menus. And once again, the terrible fit-and-finish of the WinUI3 framework is causing problems.

This time: every time the user presses the ALT key (like they are supposed to!) Windows decides to make the program BEEP!

This is reported as this bug 4379 and 9074. Some useless comments are here.

And somehow, the code I copy-pasted said that "SetWindowSubclass" et all were all in user32.dll. That's not true; it's in comctl32.dll. And did you know that if LINK.EXE isn't in your path, that DUMPBIN won't work?

Update: thanks to castorix's comment for  issue 4379, I have a workaround. See the full file at App.xaml.cs. All you have to do is weird stuff to get an HWND (ignoring the "CoreWindow" documentation since that's just wrong) and then subclass the window, catch the WM_MENUCHAR and then tell windows to close the menu. (seriously, return MNC_CLOSE, documented as "Informs the system that it should close the active menu". Telling windows to close the menu will in fact let the user open the menu with ALT keys just like it should.

Sunday, August 10, 2025

Weird issues in WebView2 / WinUI3: handling the ALT key

Applies to: WinUI3 apps with a WebView2 and a menu

Problem: Normally users press the "Alt" ("Menu") key to use an app's menu. But after the user clicks on a WebView2 (as one does; it's a common practice), all Alt keys will be swallowed up by the WebView2.

Solution: capture the 'Alt' key and use that as a trigger to move focus. 

  1. In the JavaScript for the one specific page I use, add a 'keydown' event listener. When the event.key is "Alt", send a special message to the enclosing C# code using the  window.chrome.webview.postMessage method.
  2. In the C# code add a WebMessageReceived event callback. When the special message is received, set the app's focus to the parent object (cast the this.Parent to a FrameworkElement)    

The problem with this solution is that the first ALT is entirely swallowed up. The user has to press ALT twice to use the menus, but only when they have clicked on the WebView2.

Interesting discoveries: weirdly, when I wrote the C# code, Visual Studio 2022 hallucinated up a solution that involved constructing real objects like a KeyRoutedEventArgs and then calling made-up methods. And, FYI, the KeyRoutedEventArg has no public constructor. 

Links:

KeyRoutedEventArgs  Random difficult work-around and this one, too 

So many issues: 721 3352  984 5772 288 951 

Monday, August 4, 2025

Weird issues in WebView2 / WinUI3: using the ms-appx: scheme

 Update your WebView2 WinUI3 code to read ms-appx:/// URLS from your Assets folder

It was the simplest of APIs, it was the most irritating of APIs. The ms-appx:/// URL scheme is one of the unheralded awesome parts of the WinRT API set. With it, there's a simple and straightforward way for an app to combine a bunch of C# code and also some useful HTML code. I'm using it this month for my (upcoming) Simple GPS Explorer app so that I can display GPS traces on an OpenStreetMap map.

The ms-appx:/// URL scheme is also one of the irritations of moving from UWP apps over to WinUI3 and whatever they're calling the framework. That's because the PMs decided that a simple straightforward way to read local assets wasn't useful in programming.

This blog post shows how to use ms-appx:/// in WinUI3 and WebView2 in just a few steps. We're going to combine two different abilities:

1. We're going to replace the ms-appx:/// in all URLs with an HTTP://msappxreplacement/ . This switches both the scheme (ms-appx: to http:) and adds in a fake host name (msappxreplacement)

2. We'll use the WebView2 SetVirtualHostNameToFolderMapping method s that every HTTP URL that goes to hostname "msappxreplacement" will instead read a file from a folder relative to the app.

Just to make life more difficult, the WebView2 navigation flow doesn't actually let you do this easily :-(. You'll need two chunks of code that do three things:

1. Replace the URI with the ms-appx: scheme with an HTTP URL that points to the msappxreplacement hostname. This must be done in the NavigationStarting event handler and requires that the original navigation be canceled and a new navigation started.

2. Set the NavigationStarting event handler on your WebView2 object

3. Update your WebView2.CoreWebView2 with a virtual hostname redirector so that all HTTP URLs that use msappxreplacement as the hostname instead just read from a local directory.

Code snippets:

NavigationStarting event handler must cancel the original navigation and instead set the source to this other place. You can't just update the URI because the StartingEventArgs ".Uri" value doesn't let you. This handles the item #1 above.

        private void UiWebView_NavigationStarting(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
        {
            // Input URL: ms-appx:///Assets/SimpleMapLeaflet/SimpleMapLeaflet.html
            // Updated URL: http://msappxreplacement/SimpleMapLeaflet.html
            var originalUri = args.Uri;
            if (originalUri.StartsWith("ms-appx:///"))
            {
                var replacementUri = originalUri.Replace("ms-appx:///", "http://msappxreplacement/");
                args.Cancel = true; // Cancel the original navigation.
                sender.Source = new Uri(replacementUri);
            }
        }

At initialization (like as part of your control Loaded event)

    uiWebView.NavigationStarting += UiWebView_NavigationStarting;
            await uiWebView.EnsureCoreWebView2Async();
            const String assetFolder = "Assets/HtmlSubdirectory";
            uiWebView.CoreWebView2.SetVirtualHostNameToFolderMapping
             ("msappxreplacement", assetFolder, 
               Microsoft.Web.WebView2.Core.CoreWebView2HostResourceAccessKind.Allow);


This code sets up the WebView2 to divert all URLs with an "msappxreplacement" hostname to the given directory. The directory will be in the app's installation directory (assuming you have a deployable app, of course). The "uiWebView" is the WebView2 that you put onto your XAML control. This handles the item #2 and #3 above.

Hints: the folder must exist for this to work! Otherwise the SetVirtualHostNameToFolderMapping will throw an exception.

Quick gripe: none of this should be needed. There's no reason why the WebView2 shouldn't come this way out of the box. And it's super painful that the ms-appx scheme is so far from handled by WebView2 that the only place to do the replacement is in NavigationStarting event. If the scheme isn't replaced there, WebView2 will simply fail and not even try to load the URL.


Helpful links:

See https://github.com/MicrosoftEdge/WebView2Feedback/issues/212

See also: https://github.com/microsoft/microsoft-ui-xaml/issues/1967

This restriction is not mentioned in https://learn.microsoft.com/en-us/microsoft-edge/webview2/get-started/winui