3

I am using Microsoft Visual Studio 2013 Express for Windows Desktop, writing in C++ and using the SDL 2.0.3 library. I am trying to build a stand-alone .exe which can be run on other computers without installing anything.

I have set up my project by following TwinklebearDev Lesson 0: Setting Up SDL. I am linking to the x86 version of the library and placing a x86 version of SDL2.dll in the Debug folder and the Release folder. Here is the source code:

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

int main(int argc, char **argv){
    if (SDL_Init(SDL_INIT_EVERYTHING) != 0){
        std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl;
        return 1;
    }
    SDL_Quit();

    return 0;
}

This works perfectly and returns 0 until I try to link statically. I have attempted to link statically by right clicking on the project and clicking Properties, and then setting the following:

Configuration Properties > C/C++ > Code Generation > Runtime Library > Multi-threaded (/MT)

Then I get a bunch of errors:

Warning 1 warning LNK4098: defaultlib 'msvcrt.lib' conflicts with use of other libs; use /NODEFAULTLIB:library C:\SDLtest\Project1\Project1\LIBCMT.lib(crt0init.obj) Project1

Error 2 error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) already defined in LIBCMT.lib(typinfo.obj) C:\SDLtest\Project1\Project1\msvcrt.lib(ti_inst.obj) Project1

Error 3 error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) already defined in LIBCMT.lib(typinfo.obj) C:\SDLtest\Project1\Project1\msvcrt.lib(ti_inst.obj) Project1

Error 4 error LNK2005: _exit already defined in LIBCMT.lib(crt0dat.obj) C:\SDLtest\Project1\Project1\msvcrt.lib(MSVCR120.dll) Project1

Error 5 error LNK2005: ___iob_func already defined in LIBCMT.lib(_file.obj) C:\SDLtest\Project1\Project1\msvcrt.lib(MSVCR120.dll) Project1

Error 6 error LNK1169: one or more multiply defined symbols found C:\SDLtest\Project1\Release\Project1.exe 1 1 Project1

How can I statically link SDL 2.0.3 to build a stand-alone executable that works on most computers without having to install anything?

leppie
  • 115,091
  • 17
  • 196
  • 297
user3800036
  • 105
  • 1
  • 7
  • Are you trying to statically link the Visual Studio runtime only or statically link both that and SDL? From what you've shown it looks like the first option. – Retired Ninja Sep 06 '14 at 20:52
  • Actually I only need to statically link the Visual Studio runtime, it is okay if I can just include SDL2.dll in the folder with the program. – user3800036 Sep 06 '14 at 20:53
  • 2
    I will try to save you a lot of pain and tell you to just have people install the Visual Studio runtime or provide it yourself via an installer. They will eventually have it installed anyway unless they don't use any other programs, and unless you want to build SDL from source to ensure there's no RTL mismatch with allocators and such you're just heading down a bad road. – Retired Ninja Sep 06 '14 at 21:06
  • if you don't match the DLL's runtime I believe there will be no other options but to comply with that choice – Marco A. Sep 06 '14 at 21:07
  • [This question](http://stackoverflow.com/a/37402/3800036) seems to indicate that you can statically link the VS runtime as long as you 'tell the linker to ignore the dynamically linked CRT explicitly'. Would this be a good approach (would it risk heap management issues)? How can you tell the linker to ignore the dynamically linked CRT explicitly? – user3800036 Sep 06 '14 at 21:13
  • Project Properties -> Linker -> Input -> Ignore Specific... Good luck, you'll probably need it. – Retired Ninja Sep 06 '14 at 21:31
  • Why is this a bad approach? What is wrong with it? – user3800036 Sep 06 '14 at 21:40
  • Was SDL compiled with Visual Studio 2013? If not that probably will cause you a lot more problems than this. I mean if the allocations / deallocations are not isolated you run into having 2 independent heaps. – drescherjm Sep 06 '14 at 21:44
  • I am not sure what was used to compile it. I downloaded the runtime binary and development library from [libsdl.org](http://www.libsdl.org/download-2.0.php). Now I am thinking it might be better to build it myself for static linking with MSVC. – user3800036 Sep 06 '14 at 21:48

2 Answers2

21

I've worked on this for a while, and I finally managed to statically link SDL2 on Windows. You have the option of using Cmake or Visual Studio's IDE. I prefer Cmake, but it is a bit less complicated with Visual Studio.

Prerequisites

  • CMake (Required)
  • Visual Studio 2019 (Not Required, but easier)

Step 1: Build SDL2
Download the SDL2 source from here. If you don't want to keep it in your downloads folder, I'd recommend unzipping it in your documents folder or something besides downloads. After you unzip the file, open a cmd prompt inside the folder. Make sure you go inside the folder inside the unzipped folder. (You should see a list of folders and file. If you only see one folder, then go inside that folder) Your directory should be something like ~\Documents\SDL2-2.0.14\SDL2-2.0.14. After you've gotten here, open a cmd prompt in this directory. Execute the command mkdir build && cd build. Next, execute the command cmake ... Assuming you have the default Windows 10 generator that is Visual Studio 16 2019, solutions will be created inside your build folder. If you are using a different generator, then build SDL2 as you would with that generator. You can close the command prompt now. Navigate inside your build folder, and open the ALL_BUILD.vcxproj file. This will open Visual Studio. Make sure to change your configuration to Release. Now that you have opened this, go into the properties of the SDL2 solution (right click on SDL2, then click properties at the bottom of the dialog). Now that you have the properties panel open, make sure you're under General in Configuration Properties. This should be where you open to automatically. Change the Configuration Type property from Dynamic Library (.dll) to Static Library (.lib). After that, go to Librarian, and then General. Change the end of Output File from $(TargetExt) to .lib. Now we have told Visual Studio to make a static library. Click Apply, and then press Ok. Before you build, we need to add a define in the source to let SDL2 we want to use C library functions. If you don't do this, then nothing will be able to compile. You need to find and open SDL_config.h. IMPORTANT: Make sure that you are editing the correct SDL_config.h! There are two! Make sure you are editing the one that is inside the build folder. If the SDL_config.h that you have opened only has 56 lines, then you are in the wrong one. Once you have found the right SDL_config.h, add the line #define HAVE_LIBC 1 somwehere above the line #if HAVE_LIBC. Make sure you save the file. Now right-click on the SDL2 solution, and click build. After that is finished, right-click on SDL2main, and click build. This will produce the files SDL2.lib and SDL2main.lib in ~\SDL2-2.0.14\SDL2-2.0.14\build\Release. Now that you have the static libraries, you can include them in your project.

Step 2 Option 1: Link Against Static Libs Using Visual Studio IDE
This method is a lot simpler, and for anyone reading not familiar with cmake, I'd recommend this. Note that you can use any c/c++ IDE by following the same instructions, but it will be a bit different. Open your Visual Studio solution that you want to statically link. Now go to the solution properties like we did earlier. Go to Linker->General->Additional Library Directories. Edit this list by adding the following libraries IN ORDER: SDL2main.lib SDL2.lib winmm.lib version.lib Imm32.lib Setupapi.lib libcmt.lib libucrtd.lib Note: if you don't use the editing input method provided by visual studio (clicking the down arrow to the far right of Linker->Input->Additional Dependencies), then you need to directly input these libraries with semicolons in-between. You may or may not need the libs libucrtd.lib or libcmt.lib, it will depend on your version. Make sure that you are including the SDL2 header files. After this is done, click apply, then ok. IMPORTANT: make sure that your main function has the signature int main(int, char**), otherwise your program will not compile.

Step 2 Option 2: Link Against Static Libs Using Cmake
Now we're going to be using Cmake to statically link SDL2. I'm going to be using msvc, since the runtime binaries are more easily accessible to msvc than to MinGW. For msvc we'll be using Cmake through Visual Studio. It is not necessary to use Visual Studio. All you really need is notepad and a compiler. If you were to use MinGW instead of msvc, then you would need to find the runtime binaries in Windows SDK and tell MinGW where to find them.
First, make a new Cmake project in Visual Studio. If you are presented with an introductory screen, feel free to press the button to create a CMakeSettings.json file. All this does is help configure different build types for cmake. It is not neccesary, but it can be useful. If you want a CMakeSettings.json file, but did not get the option to make one, then you can right-click a blank space in your solution explorer, and select Cmake Settings for <ProjectName>. Go into your source directory, and open the CmakeLists.txt file. Make sure that this is in the parent directory-- there are two CmakeLists.txt files, but right now we are concerned with the parent. Copy and paste the following code into that CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)

set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD")

project("tut")

add_subdirectory("src")

In the lines

set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD")

we are telling CMake to use static runtime libraries. If you want your program to be smaller, then you could use "/MD". Note that it would need the runtime dlls to run, and all that that implies. EDIT: YOU NEED TO USE /MD AND /MDd. SDL2 requires it. You can still statically link though.

add_subdirectory("src")

This tells CMake that there is more information to be found in the subdirectory "src". If you have your src folder named anything else, make sure to replace "src" with your folders name. Now go into the subdirectory "src" (or whatever yours is called) and open that CMakeLists.txt. Copy and paste the following code into that CMakeLists.txt:

cmake_minimum_required (VERSION 3.8)

add_executable(tut2 WIN32 "tut2.cpp")

target_include_directories(tut2 PRIVATE "${CMAKE_SOURCE_DIR}/include")

target_compile_definitions(tut2 PRIVATE "$<$<CONFIG:Debug>:" "_DEBUG" ">" "$<$<CONFIG:Release>:" "NDEBUG" ">" "WIN32;" "_CONSOLE;" "UNICODE;" "_UNICODE")
target_compile_options(tut2 PRIVATE /Oi; /Gy; /permissive-; /sdl; /W3; $<$<CONFIG:Debug>:/Z7>; ${DEFAULT_CXX_DEBUG_INFORMATION_FORMAT}; ${DEFAULT_CXX_EXCEPTION_HANDLING})

target_link_options(tut2 PRIVATE $<$<CONFIG:Debug>: /INCREMENTAL> $<$<CONFIG:Release>: /DEBUG; /OPT:REF; /OPT:ICF; /INCREMENTAL:NO> /NODEFAULTLIB:LIBCMT)

target_link_libraries(tut2 PRIVATE SDL2main SDL2 winmm version Imm32 Setupapi)

target_link_directories(tut2 PRIVATE "${CMAKE_SOURCE_DIR}/lib")

In the lines

target_include_directories(tut2 PRIVATE "${CMAKE_SOURCE_DIR}/include")

we are telling CMake where to look for our include files. In this example the include directory is in the source dir of our project. (The same place where our original CMakeLists.txt is located). If you feel the need to hard code in the directories, then you can just type in the absolute path to your files. Be warned, however, that this will make your code hard for others to build.

target_compile_definitions(tut2 PRIVATE "$<$<CONFIG:Debug>:" "_DEBUG" ">" "$<$<CONFIG:Release>:" "NDEBUG" ">" "WIN32;" "_CONSOLE;" "UNICODE;" "_UNICODE")
target_compile_options(tut2 PRIVATE /Oi; /Gy; /permissive-; /sdl; /W3; $<$<CONFIG:Debug>:/Z7>; ${DEFAULT_CXX_DEBUG_INFORMATION_FORMAT}; ${DEFAULT_CXX_EXCEPTION_HANDLING})

In these lines we are telling cmake all the flags we want it to compile with. If you want to know what each flag does, then this is where you should look.

target_link_options(tut2 PRIVATE $<$<CONFIG:Debug>: /INCREMENTAL> $<$<CONFIG:Release>: /DEBUG; /OPT:REF; /OPT:ICF; /INCREMENTAL:NO> /NODEFAULTLIB:LIBCMT)

In these lines we tell cmake all the flags we want to link our libraries with. If you want to know what each of these flags do, then this is where you should look.

target_link_libraries(tut2 PRIVATE SDL2main SDL2 winmm version Imm32 Setupapi)
target_link_directories(tut2 PRIVATE "${CMAKE_SOURCE_DIR}/lib")

In these lines we tell Cmake all the libraries we need to link against, and a place to look to find those libraries. Again, ${CMAKE_SOURCE_DIR}/lib is a relative path pointing to where I have put SDL2.lib and SDL2main.lib, but you can put any relative or absolute path to replace it. You can now build the project, and, if you did everything right, it should compile successfully. If you get an error saying "cannot open projectName.exe", then just re-configure your CMakeList.txt files.

If this seems way harder than it should, that's because it is. I recently had to switch back to Windows 10, and have spent a good week on what took me 2 minutes on Linux. If you have the option, I'd highly recommend giving Linux a try. All you have to do there is pass --static-libs to sdl2-config, and voila! Your program is statically linked. If you have any questions, or if there's anything I forgot to cover, let me know in the comments.

Jcsq6
  • 474
  • 1
  • 4
  • 15
0

The problem your running into is that you've configured the compiler to use static C runtime library LIBCMT.lib but some object file that you're linking with has requested that it be linked a default library. Specfically msvcrt.lib, the dynamic C runtime library. Because the two libraries define the same set of symbols you get multiply defined errors.

A possible solution is suggested in the warning message at the start of your list of errors: use /NODEFAULTLIB:library. The documentation for /NODEFAULTLIB explains how to set this option in the Visual Studio 2013 IDE:

  1. Open the project's Property Pages dialog box. [...]
  2. Click the Linker folder.
  3. Click the Input property page.
  4. Select the Ignore All Default Libraries property or specify a list of the libraries you want to ignore in the Ignore Specific Library property. The Command Line property page will show the effect of the changes you make to these properties.

The name of the default library you want to exclude is msvcrt.lib.

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • I have tried excluding `msvcrt.lib` and received 10 errors, here are the first 4: Error 1 error LNK2001: unresolved external symbol @__security_check_cookie@4 Error 2 error LNK2001: unresolved external symbol __imp__memmove Error 3 error LNK2001: unresolved external symbol _atexit Error 4 error LNK2001: unresolved external symbol __purecall – user3800036 Sep 06 '14 at 23:20
  • The `__imp__memmove` symbol indicates that one of the object files was compiled in a way that requires linking with the dynamic C runtime library. Try rebuilding your project from scratch to ensure its not one of your own object files. If that doesn't work then it looks like you'll need to recompile the SDL library so you can use it with the static C runtime library. – Ross Ridge Sep 06 '14 at 23:33