3

I'm having problems linking SDL2 to my project, which probably are because I'm new to CMake, and I don't fully know how to create new projects with it. Every time and method I've tried to fix this problem has resulted in one of the following problems:

  1. "Cannot locate WinMain@16"
  2. Multiple undefined reference errors
  3. undefined reference to `SDL_main`

I have attempted to use find_package, linking directly to files, linking to library files, following tutorials (for example https://trenki2.github.io/blog/2017/06/02/using-sdl2-with-cmake/), searching for answers (most of them talk about using find_package which I can't get to work) (for example Using SDL2 with CMake).

Project dependencies

SDL 2.0.12, CMake 3.17.0, 7-Zip, mingw32-make, wget

The project is supposed to be cross-platform, but the main devenv is Windows 10. All scripts are run from the root folder "vivaria".

Project structure

vivaria/
├── build/
│   └── [cmake build files]
├── buildtools/
│   └── SDL2/SDL2-2.0.12/lib/x86 (and x64)
│                            ├── SDL2.lib
│                            └── SDL2main.libs
├── deploy/
├── resources/
├── scripts/
│   ├── build_windows_debug_x86.bat
│   └── install_buildtools_windows.bat
└── src/
    ├── CMakeLists.txt
    └── vivaria.cpp

build_windows_debug_x86.bat

cmake -G "MinGW Makefiles" -S .\src\ -B .\build\ -DCMAKE_BUILD_TYPE=Debug
cd .\build\
mingw32-make

vivaria.cpp

#include <iostream>
#include "SDL.h"

int main() {
    if (SDL_Init(SDL_INIT_VIDEO) != 0){
        std::cout << "Hello world" << std::endl;
        return 1;
    }
    
    SDL_Quit();
    return 0;
}

CMakeLists.txt: undefined reference to SDL_main

cmake_minimum_required(VERSION 3.17)
project(Vivaria VERSION 1.0.0)

set(CMAKE_CXX_GLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -lmingw32")
# set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")

set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/../buildtools")
set(SDL2_DIR "${INCLUDE_DIR}/SDL2/SDL2-2.0.12")

set(SDL2_INCLUDE_DIRS "${SDL2_DIR}/include")
set(CMAKE_BINARY_DIR "${CMAKE_SOURCE_DIR}/../deploy")

# Support both 32 and 64 bit builds
if (${CMAKE_SIZEOF_VOID_P} MATCHES 8)
    set(SDL2_LIBRARIES "${SDL2_DIR}/lib/x64/SDL2main.lib;${SDL2_DIR}/lib/x64/SDL2.lib")
else ()
    set(SDL2_LIBRARIES "${SDL2_DIR}/lib/x86/SDL2main.lib;${SDL2_DIR}/lib/x86/SDL2.lib")
endif ()

# link dependencies
include_directories(${SDL2_INCLUDE_DIRS})
link_directories(${SDL2_LIBRARIES})

# Project files and linking
set(SOURCES vivaria.cpp)
add_executable(${PROJECT_NAME} vivaria.cpp)
target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES})

Console output

F:\Koodit\Vivaria\build>mingw32-make
[ 50%] Linking CXX executable Vivaria.exe
c:/mingw/bin/../lib/gcc/mingw32/9.2.0/../../../../mingw32/bin/ld.exe: F:/Koodit/Vivaria/src/../buildtools/SDL2/SDL2-2.0.12/lib/x86/SDL2main.lib(Win32/Release/SDL_windows_main.obj):(.text[_main_getcmdline]+0xd1): undefined reference to `SDL_main'
collect2.exe: error: ld returned 1 exit status
CMakeFiles\Vivaria.dir\build.make:104: recipe for target 'Vivaria.exe' failed
mingw32-make[2]: *** [Vivaria.exe] Error 1
CMakeFiles\Makefile2:91: recipe for target 'CMakeFiles/Vivaria.dir/all' failed
mingw32-make[1]: *** [CMakeFiles/Vivaria.dir/all] Error 2
Makefile:99: recipe for target 'all' failed
mingw32-make: *** [all] Error 2

install_buildtools_windows.bat

set SDL2=SDL2-devel-2.0.12-VC.zip
set DOWNLOAD_DIR=%cd%\buildtools
set OUTPUT_DIR=%cd%\buildtools\SDL2

wget "https://libsdl.org/release/%SDL2%" -P "%DOWNLOAD_DIR%"

7z x "%DOWNLOAD_DIR%\%SDL2%" -y -o%OUTPUT_DIR%

del /F /Q %DOWNLOAD_DIR%\%SDL2%

As you can see I'm still "a bit" new to CMake, but I'm trying to learn.

Nuubles
  • 165
  • 1
  • 13
  • Have you checked [that question](https://stackoverflow.com/questions/48723523/lnk2019-unresolved-external-symbol-c-sdl2-library) about the similar error? Its answer tells about setting `SDL_MAIN_HANDLED` macro in the source file. – Tsyvarev Jun 29 '20 at 19:09

2 Answers2

8

Oof. Everything about your CMakeLists is wrong.

  1. Never set CMAKE_CXX_FLAGS in your CMakeLists.txt
  2. Never use include_directories or link_directories
  3. Never use target_link_libraries without a visibility specifier.
  4. Never set paths to libs manually.
  5. Don't set your target names equal to the project name. It's pointless complexity and bad style.

This is all you need:

cmake_minimum_required(VERSION 3.16)
project(Vivaria VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(SDL2 REQUIRED)

add_executable(Vivaria vivaria.cpp)
target_link_libraries(Vivaria PRIVATE SDL2::SDL2)

Your code also has an error. You need to add this line to the top of your file

#define SDL_MAIN_HANDLED

First, use vcpkg to install SDL2. Then, with the above changes, this compiles and runs for me using the commands:

> mkdir build
> cd build
> cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=X:/path/to/vcpkg.cmake ..
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • I installed Ninja, changed the toolchain to vcpkg and cleared my cmakelists like you mentioned, but after attempting to run build the following error occurs: " By not providing "FindSDL2.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configuration file provided by "SDL2", but CMake did not find one". If I specify the vcpkg installation to be x64, find_package will then find the package but not consider it as the correct one to use. Neither setting -A to either x64/x86 nor setting ``-DCMAKE_GENERATOR_PLATFORM=x86`` or x64 helped (prints not supported platform). – Nuubles Jun 30 '20 at 16:32
  • You need to run vcvars or be in a developer command prompt. Make sure you actually installed sdl2 from vcpkg via `vcpkg install sdl2:x64-windows` – Alex Reinking Jun 30 '20 at 16:35
  • Also -A only applies to the Visual Studio generator, not to Ninja – Alex Reinking Jun 30 '20 at 16:36
  • After running ``vcpkg install sdl2:x64-windows`` (also tried x86) the error persists, it is installed. I'm not using VS but rather VSC (with C/C++, CMake and CMake Tools as plugins) as my IDE, is vcvarsall still required? It seems that to use vcvarsall I'd have to install Visual Studio as my IDE or grab it from somewhere to use. The errors also persist even when running the commands as administrator. I pushed the project to github for quick testing: https://github.com/Nuubles/Vivaria – Nuubles Jun 30 '20 at 17:06
  • I do not understand your answer. What if I want to distribute the required libraries together with my source project? I do not want to put the burden of installing extra dependencies on the user. I also want to keep external dependencies clean for a longer time period, because my project may take several years to finish. Your answers has "Never" a lot, but misses to explain the reasoning. – SuperTasche Apr 23 '21 at 20:47
  • 1
    @SuperTasche - I do not understand your question. None of my guidelines conflict with vendoring your dependencies or shift the burden of installing dependencies onto the user. There are good solutions in CMake for dealing with dependencies: ExternalProject (ie. a super-build), FetchContent, git submodules + add_subdirectory, etc. – Alex Reinking Apr 23 '21 at 20:50
  • 1
    @AlexReinking Thank you for the tips, I will definitely check that out! Still, it might help beginners in cmake to understand their mistakes, when expanding upon your guidelines. I have been dealing with cmake for the past couple of weeks and keep reading dogmas like "You should never do XYZ" without any reasonable rationale whatsoever. For someone whos primary goal is to get started writing projects in C++, this is a true pain. – SuperTasche Apr 23 '21 at 21:14
  • 1
    @SuperTasche - I think that would require much more detail than fits in a StackOverflow comment or an answer to a different question. If you'd like me to explain the rationale, I would be happy to do so if you would open a new SO question about it. – Alex Reinking Apr 23 '21 at 21:58
1

Thanks to Tsyvarev! Setting the macro SDL_MAIN_HANDLED in the source file fixed the problem.

#define SDL_MAIN_HANDLED // insert this
#include <iostream>
#include "SDL.h"

int main() {
    if (SDL_Init(SDL_INIT_VIDEO) != 0){
        std::cout << "Hello world" << std::endl;
        return 1;
    }
    
        std::cout << "Hello world 2" << std::endl;

    SDL_Quit();
    return 0;
}
Nuubles
  • 165
  • 1
  • 13