23 May 2024

Developing on Windows can be frustrating; while some development software is Windows-native or fully cross-platform, a lot of tools expect to be run on Linux.

There are several ways around this. My current thought process for developing is like this (I’ll explain after):

  • If your language & environment is fully supported on Windows, prefer official Windows builds. (e.g. Python, Node.js, Go, Rust)
  • If you need a tool that doesn’t rely on a Linux-like environment and has been ported to Windows, prefer the Windows version. (e.g. Git, stand-alone utilities)
  • If you need a tool that relies on a Linux-like environment, prefer MSYS2. (e.g. Bash, Make uses the shell, GCC looks for libraries in known places)
  • If you’re starting a C or C++ project that needs to link to existing compiled Windows code, use Windows build tools. (e.g. making .dll plugins for apps)
  • If you’re starting a cross-platform C or C++ project, prefer MSYS2 for MinGW-w64 GCC and a Linux-like (Make) or cross-platform (CMake, premake) build system.
  • If the development environment requires actual Linux, use WSL2.

To understand this better, we need to dig into how C and C++ development works, since Windows and Linux differ in their low-level C OS APIs.

system ABI paths Linux compat access to Win. files file access from Win. GUI GPU acceleration
native MSVC Windows none direct direct native native
MinGW-w64 MinGW-w64 Windows tiny direct direct native native
Cygwin Cygwin Windows or Linux (virtual) moderate via mounts direct native/X11 native/none?
WSL1 Linux Linux (virtual) good via mounts via share X11 none?
WSL2 Linux Linux full VM via mounts (slow) via share (slow) full Linux support OpenGL, OpenCL

The official, 100% Microsoft way of building Windows software is to use the Windows SDK, along with Visual Studio and associated tools. The official C and C++ compiler is known colloquially as MSVC (Microsoft Visual C/C++). You can install these components with the Visual Studio installer (omitting the actual IDE if you don’t want it).

MinGW-w64 is a port of Linux-based compiler GCC, and some other tools, to Windows. It produces native Windows executables. This means you can use Linux-style build scripts to build Windows C and C++ programs, instead of Visual Studio and all of Microsoft’s tools. (It’s a fork of an earlier project called MinGW.)

Cygwin also provides GCC and associated tools on Windows, but also supports compiling code written for Linux. Cygwin GCC understands lots of Linux and POSIX APIs and compiles them to Windows equivalents. It does this with the help of a runtime library cygwin1.dll that is linked into your executable/library.

MinGW-w64 and Cygwin are commonly used via MSYS2, which nicely integrates them together into a unified Linux-like environment. With MSYS2 you can use Cygwin or MinGW-w64 compilers as you choose, along with shell tools and build scripts written for Linux.

WSL1 and WSL2 are official Microsoft integrations. In contrast to Cygwin, the WSLs can run actual Linux executables, compiled on Linux.

Table columns:

  • ABI: the way compiled C or C++ code interacts with other code.
    • If your program or library wants to load another library, it must be built with the same ABI. This means you can’t compile native Windows, MinGW-w64, and Cygwin builds together.
    • This is particularly important for programming languages. For example, the official Windows Python builds use the MSVC ABI, so the ecosystem is built around that; if you try to use a different Python, you’ll be unable to use compiled, binary packages and pip will have to build them from source. Some pip packages might not work at all.
  • paths: what file paths the program sees.
    • Native and MinGW-w64-built code accesses Windows files directly.
    • Cygwin and WSL1 have virtual environments which give Linux-like paths, while still accessing Windows files.
    • If a program uses Linux paths, it may be difficult to integrate with Windows tools, as they won’t be able to see each others’ paths. Such is the case when using Windows VSCode and WSL Git. You may need to wrap the tools and convert paths to allow them to integrate; see andy-5/wslgit.
  • Linux compat: how well the full Linux API is supported, either at build time or run time.
    • MinGW-w64 compiler can compile a tiny amount of the POSIX API (which is, I think, undocumented).
    • Cygwin can compile more, thanks to a run-time library.
    • WSL1 has a compatibility layer which runs Linux executables and emulates much of the run-time Linux API.
    • WSL2 is a full Linux VM.
  • file access to/from Windows
    • direct: files are directly accessible.
    • via mounts: Windows files are accessible via mounts in a virtual Linux filesystem.
    • via network share: files are accessible via network share in Windows.
    • Note: that WSL1 files are stored as native files on disk, whereas WSL2 has a virtual hard drive and shares files over a (slower) network filesystem. This means WSL2 is faster than WSL1 accessing its own Linux files, but slower accessing Windows files.
    • Also note that Windows network shares are inaccessible to some Windows programs. You have to mount a network drive (e.g. via net use command) to give it a drive letter for direct path access.
  • GUI
    • native: Windows GUI.
    • X11: can run X GUIs on a Windows X11 server.
    • full Linux support: WSL2 has a built-in Linux window system which integrates with Windows.
  • GPU acceleration
    • none: no acceleration, since GPU is not directly accessible.
    • native: uses native APIs for full supported acceleration.
    • WSL2 has built-in OpenGL and OpenCL drivers which forward onto host DirectX for acceleration.