Porting C++ programs from Linux to Windows using vcpkg, CMake, and Visual Studio

Tutorials

Recently I had to reinstall Windows to debug a hardware issue. I decided to try to make the most of this by trying to build my game on Windows.

Installing vcpkg

First, you need to clone vcpkg. I installed the git bash program from the git website

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.bat

Next, you need to decide whether to install 32-bit (x86) or 64-bit (x64) dependencies, or both, then run one or both of the following commands:

VCPKG_DEFAULT_TRIPLET=x86-windows ./vcpkg.exe install DEPS
VCPKG_DEFAULT_TRIPLET=x64-windows ./vcpkg.exe install DEPS

where DEPS is a space-deliminated list of package names.

You can search for packages like so:

./vcpkg.exe search query

If you’re unable to find a dependency, you may have to write your own “port” file to add it to vcpkg.

Finally, to let Visual Studio know about the dependencies, run this:

./vcpkg.exe integrate install

Building

Toolchain

Import your program into Visual Studio as a cmake project. When generating the CMake cache, you’ll probably receive “not found” errors.

Right click on CMakeLists.txt and select “Open CMake Settings”. A file called CMakeSettings.json should appear, with content like the following:

{
  "configurations": [
    {
      "name": "x64-Release",
      "generator": "Ninja",
      "configurationType": "RelWithDebInfo",
      "inheritEnvironments": [
        "msvc_x64_x64"
      ],
      "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
      "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-v",
      "ctestCommandArgs": ""
    }
  ]
}

Add the vcpkg toolchain file to cmakeCommandArgs

"cmakeCommandArgs": "-DCMAKE_TOOLCHAIN_FILE=\"C:\\Users\\ruben\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake\""

Clear the CMake cache by doing CMake > Delete Cache Folders > CMakeLists.txt, then click the Generate button on the CMake changes detected banner.

This should be enough to get Visual Studio to find the dependencies, but I found that this wouldn’t work when you have custom CMake find files which probably don’t support the toolchain.

Explicit dependency paths

I’ve yet to find out how to modify the find modules to use vcpkg’s toolchain cleanly, but in the meantime I set the directories manually by adding CMake flags and environment variables.

You should prefer more general settings to individual include/lib settings. For example, findX.cmake files may allow you to specify a single environment variable to the install root instead of separate X_INCLUDE_DIR and X_LIBRARY CMake variables. This is shown below, with ENETDIR and SFML_ROOT. Don’t be too afraid to modify any embedded find scripts to support environment variables.

{
  "environments": [
    {
      "ENETDIR": "C:\\Users\\ruben\\vcpkg\\installed\\x64-windows",
      "SFML_ROOT": "C:\\Users\\ruben\\vcpkg\\installed\\x64-windows",
      "SFGUI_ROOT": "C:\\Users\\ruben\\vcpkg\\installed\\x64-windows",
      "THOR_ROOT": "C:\\Users\\ruben\\vcpkg\\installed\\x64-windows",
      "LUA_ROOT": "C:\\Users\\ruben\\vcpkg\\installed\\x64-windows"
    }
  ],
  "configurations": [
    {
      "cmakeCommandArgs": "-DCMAKE_TOOLCHAIN_FILE=\"C:\\Users\\ruben\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake\""
    }
  ]
}

Note that you may need to clear the CMake cache and regenerate for changes to take affect.

Common mistakes

Console program vs Windows program

By default, MSVC will compile your program as a console program. This mode results in Windows allocating and showing a console for you when starting the program up. This console will require a redraw on every std::cerr or std::cout print, resulting in massive performance issues.

If you program shows a graphical window, then you should change it to a Windows program.

There are three methods to do this.

  1. The first option is to set the executable type to WIN32 in CMake:

     if(WIN32)
         add_executable(${EXECUTABLE_NAME} WIN32 ${SRC})
     else()
         add_executable(${EXECUTABLE_NAME} ${SRC})
     endif()
    
  2. Second, by setting linker flags using CMake:

     if(WIN32)
         set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
     endif()
    
  3. The final option is to set the linker flags using a pragma directive:

     #ifdef _WIN32
         #pragma comment(linker, "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
     #endif
    

The ENTRY parameter allows you to use the standard main() entrypoint instead of the non-standard WinMain(). You may need to clear and regenerate the CMake cache.

Profit!

Hopefully that should be enough to get it work. Please contact me if you know of any ways to make this cleaner or more robust.