A new Windows CE x86 compiler in 2024

At ENLYZE we need to deal with operating systems from the last century on customer machines everyday. No matter whether it’s something “simple” like Windows 2000 running on an operator touch panel (“HMI”) or something peculiar like QNX 4 controlling a production machine: If they handle live production data, we need to capture this data and forward it to our pipeline. And often enough, this involves writing new custom software for these ancient operating systems.

In a perfect world, we would always use Rust for such new projects, and save ourselves from a lot of memory safety bugs and data races. And while my team at ENLYZE does use Rust wherever possible, we cannot do so in this case: For instance, Rust’s minimum supported Windows version has just been raised to Windows 10. Abandoned operating systems from the ancient past were never even supported by Rust. And even if that was not an issue, such old operating systems often come with proprietary interfaces designed around the C language (think about Windows COM or QNX message passing). Not only would we need to make Rust support the OS, but also write Rust compatibility layers around these interfaces.

This is why we went for the next best choice for writing new software on old operating systems: Modern C++
I have previously detailed in great lengths how we first added a Windows 2000 target to Visual Studio 2019’s compiler, then shifted the entire toolchain to Clang to support even older Windows NT 4.0, and finally patched the CRT to fix threading. Thanks to that work, we have been productively developing software running equally well on Windows versions as old as NT4 from 1996 and on the latest Windows 11. Modern C++ amenities like std::shared_ptr and std::unique_ptr help us to get memory management right even in an inherently memory-unsafe language.

I’m glad that we open-sourced our work and wrote about it in public, because then miracles can happen: Just a few weeks ago, open-source developer Daniel Collins picked up our repository and made it compatible with Visual Studio 2022 and latest Clang. This is something that I wanted to do for a long time, but other priorities always prevailed. His work actually deserves its own blog post.

However, today I’m talking about Modern C++ on an entirely different operating system that we encountered at customers, namely Windows CE. Despite the similar name, Windows CE has been developed from scratch for embedded devices in 1996 and shares none of the code with the popular Windows NT series that we know from desktop and server computers. Until the early 2010s, consumers mostly knew it from Windows Mobile PDAs and the first release of the ill-fated Windows Phone operating system for smartphones. The automation industry has been using it for process control systems and HMI panels for a long time. And although the last Windows CE version went out of support in October 2023, the automation industry continues to use it even for new products.

As a consequence, it was only a matter of time until we also had to develop software for Windows CE devices. And again the power of Open-Source played into our hands…

An awkward situation

We had been faced with a number of similar HMI panels from a single manufacturer. They were all running various versions of Windows CE on x86 hardware.

The last development platform officially targeting Windows CE is Microsoft’s Visual Studio 2008. Being 16 years old, this release does not even support the basics of the modern C++11 standard, let alone newer issues of the standard. Our existing code is heavily using C++17 constructs and we wanted to port as much as possible to Windows CE without rewriting it. Obviously, Visual Studio 2008 was no option here. Even if we did the massive work of rewriting our own code around an older C++ standard, we would still lack a solution for third-party dependencies: Most of them have long moved on and stopped supporting compilers as old as Visual Studio 2008.

Just like on desktop Windows, my next idea was to take a look at LLVM and Clang. Unfortunately, that compiler never had any support for Windows CE to begin with. I could have likely tweaked an existing compiler here and there, but creating an entirely new target was definitely out of scope for this project. And I was also sure that a new target for a now unsupported operating system would never be accepted upstream.

So what other options did I have?

Open-Source to the rescue

At some point, I remembered a situation from the late 2000s when fellow ReactOS developers were working on an ARM port of the Windows-compatible operating system. Notably, that was even a few years before Microsoft itself embraced the ARM architecture for desktop Windows!
Back in those days, we desperately needed a compiler targeting both ARM and Windows. Desktop Windows was exclusively x86 at that time, with no Windows ARM compiler anywhere to be found.

Our focus therefore shifted to Windows CE, the only Microsoft platform for ARM processors at that time. ARM-based Windows Mobile devices were pervasive enough in 2008 that some volunteers picked up the open-source GCC compiler and added a target for ARM-based Windows CE. They released their work as the CeGCC project. We used this compiler at ReactOS to proceed on our ARM work. Later on, the CeGCC folks also added initial support for the few x86-based Windows CE devices.

With the demise of Windows Mobile, the CeGCC project also ceased active development in 2010. The work was never upstreamed, so their adaptation of GCC 4.4 remained the last open-source Windows CE compiler for a long time.

This could be the end of the story and we would be left with another outdated Windows CE compiler – if it wasn’t for open-source developer Max Kellermann. He picked up CeGCC in 2012 and has thanklessly maintained the ARM compiler for 8 years, porting it to GCC 4.6, then 7.3, 8.2, and finally GCC 9.3 in 2020. Kudos to him for this massive endeavor!

While GCC 9.3 is obviously not the latest version by now, it’s a modern compiler that supports all of C++17 and much of C++20. Exactly what we were looking for! Well, almost: Although original CeGCC also targeted the x86 Windows CE platform, Max Kellermann only ever maintained the ARM port. There was still some work left for us to also get a GCC 9.3 targeting the x86 Windows CE platform.

Introducing GCC 9.3 for x86 Windows CE development

Now, 4 years after Max Kellermann’s last release, I picked up his work on GCC 9.3 and added the missing pieces for x86 support. Adding the target was surprisingly simple, but I have to admit that I could still look at the x86 support in original CeGCC.

What is and has always been the most difficult step is actually building GCC. Max Kellermann’s original build script offers multiple knobs and parameters, but also adds intermediate steps like manually copying over files to magic directories. Using it directly for the i386-mingw32ce target didn’t work for me.

Instead of trying to fix the existing build script, I wrote an entirely new one without any twists and options. It builds a toolchain for the given target and installs it to the given directory. This is the entire set of options you have. No surprises possible!

Thanks to my previous work on the ReactOS Build Environment, I knew the somewhat official magic voodoo order of commands to build a GCC toolchain with interleaved MinGW components for Windows development. Implementing exactly this into my script let me successfully build a working toolchain on the first attempt.

Apart from the low-level code, I also fixed some of the w32api headers to make more common control window messages available for Windows CE. Depending on how intensively you use the Win32 API dialect provided by Windows CE, this job may never be done. But the added definitions were sufficient to port our code from desktop Windows to Windows CE.

Download

I’m sure this is the section you have all been waiting for :)

My GCC 9.3 toolchain for building Windows CE binaries is available as a Docker image at https://github.com/enlyze/ghcr-windows-ce-build-environment. I provide one container for building binaries for x86 Windows CE devices as well as another container for ARM-based Windows CE devices. The GCC toolchain is configured to build standalone binaries that don’t depend on any non-standard DLLs (I’m looking at you, mingwm10.dll).

If Docker is not for you, you can always clone our repository at https://github.com/enlyze/cegcc-build and run build_cf.sh to build your own toolchain. Supported targets are i386-mingw32ce and arm-mingw32ce. There are no current plans to support further targets, but who knows what the future holds.

Building has only been tested on Linux, as this is also what our CI uses. It should be possible somehow to also get a working toolchain on a Windows or macOS host. If you’re lucky, that even works without changing the build_cf.sh script. I have not tested it.

The reward for all the work

We fulfilled our original goals by using this GCC 9.3 compiler: Most of our existing “EnlyzeInfoTool” for desktop Windows could be ported to Windows CE without changes. If changes were necessary, they were usually minimal, such as replacing calls to RegisterClassEx by RegisterClass, because the “Ex” version does not exist on Windows CE.

A picture says more than a thousand words: