I have been planning for years to write something in praise of vcpkg as making Windows compilation less unbearable. While building a binary package of
Wavepacket recently, I found, however, that there are also other issues when porting software from Unix, hence a shift in focus.
Only after programming C++ under Windows for some time did I fully grasp how much of a hacker's system Unix is. You only need to master a few concepts, such as how the linker works or which package to install for the library headers, then adding external dependencies to your code becomes almost trivial, also for others. And things just work; you might spend ages blissfully unaware of problems such as ABI compatibility, symbol versioning, linker maps or the details of the ELF symbol resolution.
This merry life comes to an end as soon as you decide to compile anything under Windows. Fundamentally, this is caused by different requirements and ecosystems.
Unix distributions thrive on Open Source Software (OSS). At their heart, they are a set of build scripts that download and build thousands of software packages. These packages are easily available through some package manager. Because of that, you can usually assume that every competent user has reasonably up-to-date system package versions and can install all dependencies.
Windows, on the other hand, was meant as an operating system for proprietary software. You have some version of Windows, possibly outdated, on which you install possibly outdated proprietary software that may have been built for an earlier version of Windows. Even worse, the proprietary software may contain
proprietary components that are themselves outdates and have been built for yet another version of Windows.
The net result of many of the subsequent design decisions is that building software on Windows is a moderate pain. This is, by the way, not restricted to OSS, commercial software also has to fight with the limitations. Let us have a look at a few consequences.
Under Windows, the user is assumed to buy software and install it through the software's installer. Package management is not offered by the system itself.
If you want to efficiently manage dependencies, your first step should be the use of a package manager. Windows-native managers such as chocolatey do not cover most development needs. For C/C++ development, you want to use a specific package manager that just builds your direct and indirect dependencies with the latest sources and makes them available to your build. From personal experience, I can recommend vcpkg, but I would assume that Conan is an equally good choice.
The general approach of vcpkg is to offer a CMake (and Meson etc.) integration that mitigates many problems. For example, it sets prefix paths such that the vcpkg are easily found by a package search or copies dlls into your output folder (see the next item).
If your code depends on other libraries, you need a dynamic loader to make these libraries available to your code at run time. As a rule of thumb, the Windows dynamic loader loads dlls only from the current directory. It does not understand runpaths, nor does Windows have common directories like /usr/lib where the libraries that you need may have been installed by default (slight simplification, but not relevant for your needs). Of course, user overrides for the dynamic loader like LD_LIBRARY_PATH do not exist, either.
The only general solution for his limitation is to copy all dependencies into the directory of an executable. Even if you build a testrunner, you need to make sure that your library under test as well as all its dependencies are copied into the same directory as the testrunner. Welcome to the state of the art for professional software development under Windows!
A common workaround is to build everything into one common output folder. Then you only need to copy the dependencies once and have them available for all programs. However, this requires of course additional build script infrastructure.
There is another problem with the dynamic loader and plugin architectures. You will usually encounter those if you build a (Python / Java / ...) extension module. Again skipping some complexities, the problem goes like this:
Now imagine Python (which depends on sqlite.dll) loading an extension module that brings an own sqlite.dll. In general, his setup will crash because either Python or the extension module gets the wrong sqlite.dll. The same can even happen if two different extension modules need the same dll.
The solution for this problem is called namespacing. If the dynamic loader is too dumb to distinguish different libraries with the same name, you make the library names unique. Hence the extension module would rename its sqlite.dll into something like sqlite_js9x81nsj.dll.
Note that this situation is usually not a problem under Unix. Ignoring some details, each ELF shared object has an own list of dependencies to search for symbols, so a Python extension module would first look into its own sqlite.so before going up the hierarchy and look into the dependencies of Python.
It should be clear by now that you cannot reasonably expect even advanced users to build your code under Windows. Instead, you have to build the code yourself, and distribute the resulting binary including all external dependencies.
This opens a new can of worms: Licenses. You might not have noticed under Unix because you only ever distribute your own source code. As soon as you distribute your binary, however, you need to fulfill all license obligations of the external dependencies.
Make sure you supply all used licenses with the binary package. And make sure that you fulfill them. While doing so, you may come across enjoyable special cases; for a harmless example the MSVC C++ standard libraries require you to adhere to the export restrictions of the United States. There may be worse conditions.
Last, but not least, Windows is simply a different platform, so you need to change the programming style.
Some other issues that I have never come across personally, but that you should be aware of:
This list of problems is by no means exhaustive, but should cover the main pain points. I hope it gives a reasonable overview of the main pitfalls and enough
pointers and keywords to solve the issue.
Personally, all the problems that I have also seen at work have made me into a very practical advocate of OSS. Once they are packaged, OSS components are easy to use and upgrade, usually make sense as a package, and do not require additional build infrastructure. This is notably different from issues that we have at work with commercial software.