The extent of Windows API compatibility

The screenshots you see signifies a pretty big step for my little game. What you see is the exact same executable running on Windows 11 24H2 (the newest Windows version to date) as well as on Windows NT 3.1 (the oldest of the NT line).
I now support every Microsoft Windows release! Well… most of them anyways…
What do you mean “most of them”?
Targeting Windows means utilizing the “Windows API”. Throughout the history, there have been multiple iterations. The first one has been retroactively named “Win16”, since the fact that the Windows versions where it originated from (Windows 1 through 3) being primarily 16 bit environments is deeply embedded into it. This not only pertains to pointers being 16 bit, but includes things such as segmented memory and many assumptions related to how 16 bit Windows (had to) work.
This game is made using NRender, my own little custom middleware for 3D rendering, audio, resource management and stuff like that. NRender has quite a few design decisions baked in. One of them is that it really needs at least a 32 bit CPU and a mostly flat address space (at least, for it’s own stuff) to work. Thus, Win16 is pretty much a no-go.
When developing Windows NT, Microsoft not only removed segmented memory and expanded pointers to 32 bits, but took the opportunity to overhaul the Windows API as a whole. They aptly named this new API “Win32”. Applications now run 32 bit code in a flat address space: Perfect for NRender! This is what Windows NT natively runs and all Windows versions today, as descendants of Windows NT, still use (adapted to 64 bit).
So, that’s what I mean by “most of them”: Every Windows version that implements Win32 to some capacity. On the NT line, this includes all of them. On the DOS line, this includes 9x/Me and (maybe a bit surprisingly) Windows 3.1.
Subsets and subsets of subsets
Unlike standards like POSIX or SUS, Win32 isn’t properly specified. What functionality is included and what it’s limitations are are not formally defined, but instead derive from “whatever a specific version of Windows does”.
But not for lack of at least trying! Microsoft, early on, specified 3 levels of Win32 API:
- Win32: The full implementation as part of Windows NT
- Win32c: A limited implementation (lacking Unicode, for example) as part of what would become Windows 95. “c” for “compatible”
- Win32s: An even more limited implementation through an addon (also called Win32s) for Windows 3.1. “s” for “subset”
Microsoft quickly dropped the “Win32c” naming, but still kept the technical differences and even continued Win32s development.
The subset one targets doesn’t tell the whole story, though. As Windows evolved over the years, so did it’s APIs. It is entirely possible to have functionality present and work on a Win32c implementation, like Windows 95, but not on an older “full” Win32 one like Windows NT 3.1.
So, which one to use?
When writing the Win32 platform support for NRender, I’ve decided to target Win32c first and maybe tune things to also comply with Win32s later on. This worked really well. The resulting binary would work nicely on Windows 95 and Windows NT 4.0.
It also happened to mostly work for on Windows 3.1 (after implementing some stuff it’s GDI wouldn’t), though somewhat slower. I haven’t tried NT 3.x at this point. Functionality would be limited on both of them, however. NRender makes use of OpenGL 1.1. Windows 3.1 doesn’t provide OpenGL at all, while Windows NT 3.x only provides OpenGL 1.0 (which, most importantly, lacks texturing). But the software renderer works nicely.
It also ran well on Wine and ReactOS of course, two free software re-implementations. There are actually more Windows API re-implementations than you might think. For once, there is HX DOS Extender, which does implement a sub-sub-subset of Win32. After some fiddling, the Win32 build ran there as well! This was the first time running NRender on DOS, though a native DOS+DPMI port would come later.
Digging into NT 3.x
So at this point, NRender worked on every DOS based Windows version that allowed running Win32 applications. It also ran on all Windows NT versions from 4.0 onwards, but I wanted to tackle 3.x as well. That is: Windows NT 3.51, Windows NT 3.5 and Windows NT 3.1.
Turns out, Windows NT 3.51 was really easy: It worked all the time! I just haven’t tried it. Windows NT 3.5 was just missing some calls related to display settings (EnumDisplaySettings and ChangeDisplaySettings), so I’m calling them dynamically (and just don’t support changing display settings on <= NT3.5).
This also should make the binary run on older Win32s versions. In turn, this would, in theory, make the Win32 build run on OS/2. which only supports Win32s 1.25(ish?) and even then only on Warp 4 it seems. I haven’t tested that yet, though.
Windows NT 3.1, on the other hand, was more difficult. First, it
refuses to run executables that have anything higher than “3.10” set as
their Win32 subsystem version. This is just a field in the .exe’s
header, which is easy to fix. Sadly, it also changed behaviour for many
other Windows versions: Native widgets (as used in the configuration
dialog) will appear with their 3.x style:


There are ways around that, but I decided to let it be for now...
Another thing was actually caused by MinGW: It’s dirname() implementation calls IsDBCSLeadByteEx(), which NT 3.1 doesn’t provide. So I shimmed it, calling it dynamically when present and just dummying out when not. This breaks directory access for paths that contain Unicode characters on NT 3.1…oh well.
Together with a few other tweaks to long file name handling and windows version detection, this makes the game run on NT 3.1!
Where we are now
Right now, the same sgame.exe executable will run on any Windows version that is officially capable of running 32 bit windows applications. This begins on Windows 3.1 (with Win32s) and Windows NT 3.1, through Windows 9x, NT 4.0, 2000, XP and so on all the way up to the latest Windows 11 24H2. It also runs on HX DOS Extender, Linux and BSD with Wine and ReactOS.
Can we do even better?
Considering all Windows versions that can run Win32 applications through Microsoft-provided means, this is as far as I can go: I do support every single one.
There is one more target, however: I could, in theory, support Windows 3.0 via use of Watcom's “Win386” windows extender. This takes the concept of DOS extenders, shoehorning a 32 bit application on top of the 16 bit DOS application and translating between them when necessary, and applies it to Windows 3.0. I haven’t played around with that too much yet (and probably won’t for the time being).
Get Apus
Apus
More posts
- The game has a name!8 days ago
- Unforeseen technical difficulties30 days ago
- The first preview55 days ago
- Technical roundup76 days ago
- The prototype before the final prototype91 days ago
- Exercising the entity systemMar 29, 2025
- Prototype 2Mar 22, 2025
- Let's start an experimentMar 15, 2025
Leave a comment
Log in with itch.io to leave a comment.