MSVC Compilation Funtimes

Published by

on

Good grief has it been a rough few days. My last post was talking about how I got encryption working. After which I decided to try to get the client-side part of the code working in Unreal Engine/Windows. Little did I realize the nightmare that is porting to MSVC.

The first step I knew. I needed to write preprocessor guards around posix specific stuff such as epoll.

#if defined(_WIN32)
#include <WinSock2.h>
#else 
#include <netinet/in.h>
#include <sys/socket.h>
#endif

namespace net 
{
    class Socket {
    public:
        Socket(const std::string IP, const int Port) : IP(IP), Port(Port)
        {
            SocketFileDescriptor = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            std::memset((char *)&Addr, 0, sizeof(Addr));
            Addr.sin_family = AF_INET;
            Addr.sin_port = htons(Port);
        };
#if defined(_WIN32)
		# TODO Windows constructor here if it requires it
#else
        Socket(const Socket& other) : IP(other.IP), Port(other.Port), 
            SocketFileDescriptor(other.SocketFileDescriptor), 
            EpollFileDescriptor(other.EpollFileDescriptor), Addr(other.Addr) {};
#endif

Pretty standard stuff, just define preprocessors around OS specific includes and types. I haven’t actually ported or implemented IoCompletion sockets yet, that’s next. First I had to get my code to actually compile in Windows.

Problem #1: min/max

Apparently windows.h defines macros for min/max, I did not know this. Guess what is a C++ standard algorithm? min and max! This led to tons of errors because std::min/std::max is used all over the FlatBuffers code base. But of course you don’t get easily deducible errors in C++, you get this:

2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/verifier.h(38,23): warning C4003: not enough arguments for function-like macro invocation 'max'
2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/verifier.h(38,23): error C2589: '(': illegal token on right side of '::'
2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/verifier.h(38,12): error C2062: type 'unknown-type' unexpected
2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/verifier.h(38,12): error C2059: syntax error: ')'
2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/vector_downward.h(39,51): warning C4003: not enough arguments for function-like macro invocation 'max'
2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/vector_downward.h(39,51): error C2589: '(': illegal token on right side of '::'
2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/vector_downward.h(285,2): message : see reference to class template instantiation 'flatbuffers::vector_downward<SizeT>' being compiled
2>C:\code\pmo\build\_deps\flatbuffers-src\include\flatbuffers/vector_downward.h(37,1): error C2059: syntax error: ')'

Apparently the way to fix this is to define NOMINMAX before you include windows.h for the first time:

#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <ws2def.h>
#include <Windows.h>
#else 
#include <netinet/in.h>
#endif

In my CMakeLists.txt file I had to then set:

# All CMakeLists.txt that include FlatBuffers need this, not just pmo_library
target_compile_definitions(pmo_library PRIVATE NOMINMAX)

OK so those errors disappeared on to the next problem!

Problem #2: static libsodium linking

This one took me, no joke, 4 days to figure out. I just for the life of me could not get libsodium to be statically compiled. Part of it (ok almost all of it) is due to my lack of knowledge of the various moving parts of:

  • How CMakeLists.txt generates a MSVC project file
  • How libsodium is ultimately compiled
  • How libsodium is linked
  • How libsodium is linked in a MSVC environment.

If you remember from our project setup. I have a CMakeLists.txt solution with a bunch of sub-projects. One for the client/server app, one for the pmo_library, and one for the testlib. Since libsodium doesn’t have it’s own CMakeLists.txt, you have to use the find_sodium cmake file from Facebook. It takes in a bunch of arguments when configuring for MSVC:

# Top level CMakeLists.txt

set(sodium_USE_STATIC_LIBS ON)
if (MSVC)
  set(sodium_DIR ${CMAKE_SOURCE_DIR}/third_party/libsodium-1.0.18/)
  set(sodium_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/third_party/libsodium-1.0.18/src/libsodium/include/)
  set(sodium_LIBRARY_DEBUG ${CMAKE_SOURCE_DIR}/third_party/libsodium-1.0.18/Build/Debug/x64/libsodium.lib)
  set(sodium_LIBRARY_RELEASE ${CMAKE_SOURCE_DIR}/third_party/libsodium-1.0.18/Build/Release/x64/libsodium.lib)
  link_directories(${sodium_LIBRARY_DIRS})
endif()
find_package(sodium REQUIRED)

I ended up just prebuilding the libsodium.lib files directly from their included MSVC .sln project file, then linking directly to it. When trying to build testlib, I started seeing unresolved symbol errors:

5>pmo_library.lib(cryptor.obj) : error LNK2019: unresolved external symbol __imp_crypto_aead_xchacha20poly1305_ietf_encrypt referenced in function "public: class std::unique_ptr<class std::vector<unsigned char,class std::allocator<unsigned char> >,struct std::default_delete<class std::vector<unsigned char,class std::allocator<unsigned char> > > > __cdecl crypto::Cryptor::Encrypt(unsigned int,class std::array<unsigned char,24> &,unsigned char const *,unsigned __int64)" (?Encrypt@Cryptor@crypto@@QEAA?AV?$unique_ptr@V?$vector@EV?$allocator@E@std@@@std@@U?$default_delete@V?$vector@EV?$allocator@E@std@@@std@@@2@@std@@IAEAV?$array@E$0BI@@4@PEBE_K@Z)
5>pmo_library.lib(cryptor.obj) : error LNK2019: unresolved external symbol __imp_crypto_aead_xchacha20poly1305_ietf_decrypt referenced in function "public: class std::unique_ptr<class std::vector<unsigned char,class std::allocator<unsigned char> >,struct std::default_delete<class std::vector<unsigned char,class std::allocator<unsigned char> > > > __cdecl crypto::Cryptor::Decrypt(unsigned int,unsigned char const *,unsigned char const *,unsigned __int64,unsigned __int64 &)" (?Decrypt@Cryptor@crypto@@QEAA?AV?$unique_ptr@V?$vector@EV?$allocator@E@std@@@std@@U?$default_delete@V?$vector@EV?$allocator@E@std@@@std@@@2@@std@@IPEBE0_KAEA_K@Z)
5>pmo_library.lib(cryptor.obj) : error LNK2019: unresolved external symbol __imp_crypto_aead_xchacha20poly1305_ietf_keygen referenced in function "public: class std::shared_ptr<class std::array<unsigned char,32> > __cdecl crypto::Cryptor::GenerateKey(void)const " (?GenerateKey@Cryptor@crypto@@QEBA?AV?$shared_ptr@V?$array@E$0CA@@std@@@std@@XZ)
5>pmo_library.lib(cryptor.obj) : error LNK2019: unresolved external symbol __imp_crypto_box_keypair
...

This was baffling, because A, it works in linux. B. I’m clearly linking to sodium in my testlib:

# tests/CMakeLists.txt
target_link_libraries(testlib PRIVATE Catch2::Catch2 flecs_static sodium flatbuffers pmo_library)

I spent 3 days (well 3 nights, I work on this like 1-2 hours a night) and just could not figure out why I was getting unresolved symbols. These errors occur because when it comes to link all the obj’s together it can’t find them, but as you can see in my testlib, it’s right there! I even checked my MSVC solution and sure enough it had a direct path to libsodium.lib as well!

After much head scratching, I found a post about how sometimes the linker needs libraries to be in a specific order, so I checked testlib, and moved sodium to be before pmo_library. This did not work.

Then I looked at pmo_library cmake:

target_link_libraries(pmo_library PRIVATE flecs_static fmt::fmt flatbuffers)

Guess what’s missing …

sodium

The pmo_library needs to link to the libsodium as well! After fixing that I finally could sort of compile. I started to see this weird warning though…

Problem 3: /MD /MDd /MT /MTd

Welcome to the wonderful world Microsoft Run-Time Library. I was seeing this strange warning appear:

5>MSVCRT.lib(initializers.obj) : warning LNK4098: defaultlib 'libcmt.lib' conflicts with use of other libs; use /NODEFAULTLIB:library

I don’t like warnings. So I looked it up and found this helpful stackoverflow post. Sure enough when I checked my MSVC project properties, all my projects were set to /MD. But I need /MT! After fussing around with tons of different “solutions” none worked except this:

# Top level CMakeLists.txt

if(POLICY CMP0091)
  cmake_policy(SET CMP0091 NEW) 
endif()

set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedlt;lt;CONFIG:Debug>:Debug>")

project(pmo ...)

Note you HAVE to set this policy before you define the project. Because, sure, this is totally normal naming for a build system and is super obvious.

After all of this work I am finally able to run my single, statically linked executable, horray me!

Now I get to figure out how to stuff this library inside of Unreal Engine which I am sure will be another nightmare.