A self-contained cross-compilation toolchain for classic Windows CE and
Windows Mobile devices. It builds Max Kellermann's WinCE forks of GNU
binutils and GCC (9.2) together with the mingw32ce runtime (mingwrt,
w32api) entirely on your Linux machine, and wires them up so that
compiling, deploying, and running test binaries is a one-command affair.
Import libraries for each target OS come from the official Microsoft SDKs. The SDK installers are downloaded, checksum-verified, and mined statically, so nothing proprietary is committed to this repository and no Windows machine is ever needed.
- A complete
arm-wince-mingw32ceGCC toolchain: C and C++, with working exception handling and the full libstdc++ built for the target. - Per-flavor sysroots. Switching between Windows Mobile 5, Windows Mobile 6, and plain Windows CE 5.0 is one command; import libraries are overlaid to match.
- A scriptable test bench: Microsoft Device Emulator 3.0 installed into a
private Wine prefix, booting real OS images, with a shared folder acting
as
\Storage Card. - CMake integration that tracks the installed flavor automatically,
including the correct
_WIN32_WCEvalue.
From zero to a hello world running in an emulated Windows Mobile device:
git clone https://github.com/Hedy88/ce-toolchain.git
cd ce-toolchain
python3 -m pip install --user -e '.[dev]'Build the compiler once. This is the long step (tens of minutes; see below for details):
ce-toolchain build-toolchainPick a target OS and verify the compiler end to end:
ce-toolchain install-sysroot wm6-professional-arm
ce-toolchain smoke-testSet up the emulator (one-time download of Microsoft's Device Emulator 3.0), then compile, deploy, and run:
ce-toolchain emulator-setup
ce-toolchain deploy
ce-toolchain emulator-runAn emulated device boots. In its File Explorer, open \Storage Card and tap
the deployed exe.
Pass any of these to install-sysroot:
| Flavor | Target OS | CE version | Emulator images |
|---|---|---|---|
wm5-pocketpc-arm |
Windows Mobile 5.0 Pocket PC | 5.1 | yes |
wm6-professional-arm |
Windows Mobile 6 / 6.1 Professional | 5.2 | yes |
ce5-standard-arm |
plain Windows CE 5.0 (Standard SDK) | 5.0 | none (see note) |
Notes:
- There was never a dedicated WM6.1 SDK. The
wm6-professionalflavor covers both 6.0 and 6.1 Professional devices. - The Standard SDK targets generic Windows CE without the Windows Mobile stack (no phone, PIM, or ActiveSync APIs). It ships no ARM emulator images because the CE5-era emulator was x86-based. To test such builds, either use real hardware or boot a WM image instead; Windows Mobile is a superset of the same CE 5.x kernel. A generic community-built CE 5.0 image also works well in the emulator (see the emulator section).
- Flavor names ending in
-armare sysroot directories undersysroot/. The bare forms (wm5-pocketpc, ...) are SDK keys used byfetch-sdk.
Debian/Ubuntu:
sudo apt install git make gcc g++ flex bison texinfo \
msitools p7zip-full wine cmake python3-pipFedora/RHEL:
sudo dnf install git make gcc gcc-c++ flex bison texinfo \
msitools p7zip wine cmake python3-piptexinfoprovidesmakeinfo, needed by the GCC docs step.msitoolsprovidesmsiextract; together withp7zipit lets us unpack MSI installers without executing them.wineruns the Device Emulator. A 64-bit Wine installation is expected; the tooling creates its own win32 prefix at~/.wine-ce.- CMake 3.25+ is only needed for the CMake example and your own projects.
- Python 3.10+ hosts the orchestration CLI.
Check everything at once with:
ce-toolchain doctorOne command does the whole bootstrap in dependency order:
ce-toolchain build-toolchainInternally this runs four stages, each stamped so an interrupted build resumes where it left off by simply re-running the command:
- binutils fork (assembler, linker,
objdumpand friends for PE/CE) - GCC first stage, enough to build runtime code
mingwrtandw32api(the C runtime and Win32 headers/libs). These two must be cloned side-by-side before either is built, since each compiles against the other's source headers.targetlibs: the full libgcc plus libstdc++ against the finished runtime
Expect tens of minutes on a modern CPU, dominated by the GCC stages, and
roughly 3 GB of disk across src/, build/, and the installed tree under
toolchain/. The result is a self-contained prefix: no system paths are
baked in, so the tree can be tarballed and moved.
Use WSL2. The tooling assumes Linux userland (msiextract, wine, bash
staging); a native MSYS2 build is not supported.
-
From an admin PowerShell:
wsl --install -d Ubuntu. Reboot, create your Linux user. -
Follow the Linux sections above verbatim inside Ubuntu.
-
Keep the repository on the Linux filesystem (for example
~/projects/ce-toolchain). Working under/mnt/cslows builds down by an order of magnitude. -
For emulator GUI output, Windows 11 shows Wine windows automatically via WSLg. On Windows 10, install VcXsrv or X410, start it with native OpenGL disabled, and point the display at it before
emulator-run:export DISPLAY=$(grep nameserver /etc/resolv.conf | awk '{print $2}'):0
One-time setup downloads Microsoft Device Emulator 3.0 (SHA-verified, hosted
on the Internet Archive), extracts it statically, and installs it into the
private ~/.wine-ce prefix along with every OS image staged by your
sysroots:
ce-toolchain emulator-setupCompile something, put it where the device can see it, and boot:
ce-toolchain smoke-test
ce-toolchain deploy # copies exes to build/device-storage
ce-toolchain emulator-run # boots the default image of the active flavorInside the emulator, \Storage Card maps directly to
build/device-storage, so anything deployed shows up there. Open it in File
Explorer and tap an exe.
Useful variations:
# pick another staged image explicitly
ce-toolchain emulator-run --image wm6-professional/PPC_USA_GSM_VR.BIN
# pass raw DeviceEmulator.exe flags after
ce-toolchain emulator-run -- /memsize 256 /video 800x600x16Screen resolution belongs to the emulator and can be set per launch with
/video WxHxBPP. To persist a resolution for a specific image, place a
sidecar file named <image>.args next to the staged image containing the
flags; they are applied automatically on every launch.
Shell locale, by contrast, is baked into the OS image itself and cannot be changed at launch time.
Everything the toolchain produces is a normal ARM executable. Copy it to the device over USB MassStorage or ActiveSync as usual and run it from there. The emulator's ActiveSync/cradle features do not work under Wine, which is why deployment goes through the shared folder instead.
Two zero-flag options; pick whichever suits your workflow.
Option A: source the environment once. After this, plain cmake
invocations use the cross compiler without further flags:
. ./setup-env.sh
cmake -S examples/cmake-app -B build/plain
cmake --build build/plainOption B: CMake presets, picked up by VSCode, CLion, and Rider too.
Copy examples/cmake-app/CMakePresets.json into your project:
cd examples/cmake-app
cmake --preset wince
cmake --build --preset winceBoth routes point CMake at the checked-in
cmake/arm-wince-mingw32ce.cmake, which sets CMAKE_SYSTEM_NAME to
WindowsCE, selects the cross compilers, restricts find_* searches to the
target sysroot, and derives _WIN32_WCE from whichever flavor is currently
installed (so flavor-gated SDK headers behave correctly).
Then deploy and run:
ce-toolchain deploy build/presets/hello-cmake/hello-cmake.exe
ce-toolchain emulator-runWhat works:
- Targets: ARMV4I Windows CE 5.x, in the three flavors listed above.
- Languages: C and C++ with working exception handling. GCC is a 9.2 fork
defaulting to GNU C++17;
<optional>,<variant>,<string_view>,<atomic>, CTAD, and structured bindings all work. - Linking: static by default. libgcc and libstdc++ are linked in, giving
roughly 10 MB unstripped binaries; strip with
-s. - Testing: full compile/deploy/run loop in the Device Emulator under Wine.
Known limitations:
- ARM only. The binutils and GCC forks only wire up the
arm-wince-pe/mingw32cetargets. x86, MIPS, and SH would need genuine porting. - Soft-float only. Configured with
--disable-multilibbecause the fork'st-wince-pefragment declares an-mfloat-abi=hardmultilib that its own backend cannot compile. - libgcc provides no
__cxa_atexit/__dso_handle(configured with--disable-__cxa_atexit). Irrelevant for single statically-linked binaries. - No threads:
std::threadand friends are unavailable (libstdc++ configured with--disable-threads; WinCE has no pthread layer). Single-threaded atomics are fine.<filesystem>is likewise unavailable. - C++20 is partial at best (
-std=c++2agives concepts; most C++20 library pieces arrived in GCC 10+). - The emulator on Wine is CLI-driven: no skins, no Device Emulator Manager, no cradle, hence no ActiveSync/RAPI connection. Deployment uses the shared folder.
| Path | Contents |
|---|---|
src/ |
shallow clones of the upstream forks (WinCE patches live as branches) |
build/ |
per-component build trees and stamp files |
toolchain/cegcc/ |
the installed toolchain (arm-wince-mingw32ce-*) |
sysroot/<flavor>/ |
staged SDK headers, import libs, and emulator images per flavor |
downloads/ |
SDK installers kept on disk; extraction is quick |
ce_toolchain/ |
the Python CLI orchestrating all of the above |
BSD 2-Clause for everything in this repository. Downloaded Microsoft SDKs and the Device Emulator remain governed by their own terms and are fetched at build time rather than redistributed here.
