4

I'm trying to add SDL2 as a library to my project. I want to link it statically. I'm new to c++.

1 - Why does the SDL website recommend linking dynamically whenever possible?

I understand the benefits of dynamic libs. However, assuming users will have all the libraries you need already installed and ready to go in their system is a pretty big assumption IMO. The only case where linking dynamically sounds like a good idea to me is where you are using well know libraries that ship with the OS/platform. https://wiki.libsdl.org/Installation

2 - Linking dynamically seems to automatically find the intrinsic dependencies of (SDL2 and SDL2_image). Linking statically does not. Why is this the case? Here's my FindSDL2_image.cmake file


find_path(SDL2_IMAGE_INCLUDE_DIR SDL_image.h)
include_directories(${SDL2_IMAGE_INCLUDE_DIR})

# PREFER STATIC LIBRARIES ########
# cmake respects the order of extensions when looking for libraries
SET(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
# ------------------- ########

find_library(SDL2_IMAGE_LIBRARY NAMES SDL2_image PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX})
set(SDL2_IMAGE ${SDL2_IMAGE_LIBRARY})

This links sdl2_image statically. It doesn't link properly because Undefined symbols:

  "_png_set_strip_16", referenced from:
      _IMG_LoadPNG_RW in libSDL2_image.a(IMG_png.o)
  "_png_set_write_fn", referenced from:
      _IMG_SavePNG_RW_libpng in libSDL2_image.a(IMG_png.o)
  "_png_write_png", referenced from:
      _IMG_SavePNG_RW_libpng in libSDL2_image.a(IMG_png.o)

If I remove the section ### PREFER STATIC LIBRARIES ## on the cmake file. It links dynamically and everything works as expected. Why when linking dynamically the intrinsic dependencies are resolved but not when linking statically?

----UPDATE----

I was able to link sdl2_image statically by including its dependencies explicitly

find_library(PNGLIB png)
find_library(JPEG jpeg)
find_library(TIFF tiff)
find_library(WEBP webp)
find_library(LZ z)

target_link_libraries(smb ${SDL2} ${PNGLIB} ${JPEG} ${TIFF} ${WEBP} ${SDL2_IMAGE} ${LZ})

However, this will not scale well for me. Figuring out what these dependencies took a bit of guesswork and googling. Ideally, I'd like CMAKE to pull these in automatically.

frankelot
  • 13,666
  • 16
  • 54
  • 89
  • Everything works as expected? I think that that might be a bit premature .. better to say that everything *seems* to work as expected. :) What does 'ldd' say about your dynamic executable? Is the dynamic linker looking for and finding libpng.so.x ? – Andrew Atrens Apr 22 '20 at 17:54

4 Answers4

4

it looks like there are several questions, I'll try my best to answer the question step by step:

Why does the SDL website recommend linking dynamically whenever possible

One of the reason to link you application dynamically against a library is to decouple the application from the library (it is called shared library / .so in this case). You could update your library without the necessity of recompiling your application code. E.g. in case you have finished your project, and your client have your application running, I suppose, it is not likely that you want to recompile your application code, once there is a bug fix of the underlying library you are using.

On the other side, by linking your application statically, you're binding your application with that library (.lib or .a form). Means every changes in the library will cause you to recompile your code. Sometime this is wished, e.g. you have provide your client a warranty of your application, usually you want to be sure that no future problem with your library would cause your application to be crashed.

I have a short example code to understand this better: CMakeLists.txt:

cmake_minimum_required (VERSION 3.0)
project(linkageLibrary)

set(STATIC_LIB lib_static)
set(SHARE_LIB lib_share)

set(STATIC_OTHER_LIB lib_otherstatic)
set(SHARE_OTHER_LIB lib_othershare)


add_library(${STATIC_LIB} STATIC ${STATIC_LIB}.cpp)
add_library(${SHARE_LIB} SHARED ${SHARE_LIB}.cpp)

# not yet in usage...
add_library(${STATIC_OTHER_LIB} STATIC ${STATIC_OTHER_LIB}.cpp)
add_library(${SHARE_OTHER_LIB} SHARED ${SHARE_OTHER_LIB}.cpp)

add_executable(${CMAKE_PROJECT_NAME} main.cpp)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${${CMAKE_PROJECT_NAME}_SOURCE_DIR})
target_link_libraries(${CMAKE_PROJECT_NAME} ${STATIC_LIB} ${SHARE_LIB})

file(WRITE ${CMAKE_BINARY_DIR}/exchangeShareLibrary.sh "
 echo \"before exchange the static library\"
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME} &&
 mv ${CMAKE_BINARY_DIR}/lib${SHARE_LIB}.so ${CMAKE_BINARY_DIR}/lib${SHARE_LIB}.so.bk &&
 cp ${CMAKE_BINARY_DIR}/lib${SHARE_OTHER_LIB}.so ${CMAKE_BINARY_DIR}/lib${SHARE_LIB}.so &&
 echo \"after the shared library has been changed\" &&
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}")

file(WRITE ${CMAKE_BINARY_DIR}/exchangeStaticLibrary.sh "
 echo \"before exchange the static library\"
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME} &&
 mv ${CMAKE_BINARY_DIR}/lib${STATIC_LIB}.a ${CMAKE_BINARY_DIR}/lib${STATIC_LIB}a.bk &&
 cp ${CMAKE_BINARY_DIR}/lib${STATIC_OTHER_LIB}.a ${CMAKE_BINARY_DIR}/lib${STATIC_LIB}.a &&
 echo \"after the static library has been changed\" &&
 ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}")

main.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

int main() {
    printStaticLib();
    printShareLib();
    return 0;
}

lib.hpp:

#pragma ONCE

void printStaticLib();
void printShareLib();

lib_static.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printStaticLib() {
    cout << "linkage of lib_static" << endl;
}

lib_share.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printShareLib() {
    cout << "linkage of lib_share" << endl;
}

lib_otherstatic.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printStaticLib() {
    cout << "linkage of the other lib_static with other text" << endl;
}

lib_othershare.cpp:

#include <iostream>
#include "lib.hpp"
using namespace std;

void printShareLib() {
    cout << "linkage of the other lib_share with other text" << endl;
}

if you run the generated scripts, you'll notice the printShareLib() will output differently after the .so get exchange, but not the printStaticLib(). (Try to make again without clean-up, if you now execute ./linkageLibrary, you'll get this time also other output for the printStaticLib(). Can you guess why?)

to your second issue:

2 - Linking dynamically seems to automatically find the intrinsic dependencies of (SDL2 and SDL2_image). Linking statically does not. Why is this the case?

without knowing your system setup, I guess the .a static library couldn't be found by your build system. Try to find where it is, and eventually put find_library(SDL2_IMAGE_LIBRARY NAMES SDL2_image HINTS ${_YOUR_SDL2_INSTALLATION_PATH})link

now back to your question, why SDL2 suggested to be linked dynamically, well it is the decision of the library developer, as you could read in his README

Please also refer this blog of how using SDL2 with CMake

dboy
  • 1,004
  • 2
  • 16
  • 24
  • Thanks for taking the time to answer! I understand the benefits of dynamic libs. What don't get is why would anyone want to link SDL dynamically, and why SDL even suggests it. There's a high chance that the final user of your software doesn't have it installed in their system and the app will not even launch. Assuming the library is present on your user's computer is a huge risk to take just to have the potential ability to maybe update to a newer version of SDL in the future. Linking dynamically makes sense for platform libraries (libraries that already ship with the OS) – frankelot Apr 28 '20 at 15:09
  • Well, at least for linux you have it e.g. debian package. Advantage: installation of SDL could be done by sysadmin instead of developer (they are usually cheaper). Other advantage: you keep you code tidy, and incase you are using costy library, you client have to buy this instead the developer. That is the reason why people want to link their app dynamically. Usually devs would only link their app, if they are really have to. (If you are linking against big librarys, your app will grow ) So at the end it is the question of preference, why they suggest to link dynamically. – dboy Apr 28 '20 at 15:37
  • I guess, you've read some literature about them, maybe is [this](https://www.ibm.com/support/knowledgecenter/en/ssw_aix_71/performance/when_dyn_linking_static_linking.html) also interessting. But one point I maybe forgot to mentioned, the cpp (libraries) development (C doesn't have shared libraries) is also thigh with the development of the UNIX/Linux system, so relying of library handling from the OS is considered as lightweight, and error prune. Don't forget, you'll only need `apt install libSDL2-dev` to install it in your distro. – dboy Apr 28 '20 at 16:03
  • I'm know linking dynamically has its advantages, I'm talking about SDL specifically here. SDL is mostly used for making games. I want to be able to send a file to my friends or upload a link to the internet and have people playing the game without me having to worry about what distro of Linux they have or putting up a complex tutorial on how to download SDL. Some distros of Linux might come with SDL pre-installed, still, I would not automatically assume that all of them have it. – frankelot Apr 28 '20 at 17:27
  • Ok, now I understand your use case. Anyway, I guess the answer why they recommend to link dynamically could only be answer by the developer (besides the obvious reason I try to provide you) please find their [README](https://hg.libsdl.org/SDL/file/default/docs/README-dynapi.md) , special to your issue, I'll see what I can do. – dboy Apr 28 '20 at 19:47
  • I read the link you provided and it was partly because of that that I asked this SO question. That README seems to give even more reasons to link statically. They added a level of indirection so "[...] Now developers can statically link SDL, and users can still replace it!".. They still recommend linking statically. "[...](We'd still rather you ship a shared library, though!)". – frankelot Apr 28 '20 at 20:34
  • Ok, now I read the whole text until the end, and it realy sound inconsistent. I give up... – dboy Apr 28 '20 at 20:50
  • Cool! so it's not just me... I've decided to disregard their advice and link statically. I've granted the bounty to you, thanks for the chat :) – frankelot Apr 28 '20 at 22:05
2

A Static library is merely a collection of compiled object files. It does not have any additional information about its dependent libraries. You can use the ar -t &ltstatic_lib&gt command to view the contents of a static library. So if you need to link your executable to a static library, you have to provide the names and paths of all its dependent libraries.

For example: Consider two static libs, A and B and assume that A depends on B. Then if want your exe C to link to A, then your link statement should contain both A and B. Then only all the symbols will defined properly.
Linker: C ===> A and B

A dynamic library is different and more intelligent than a static. It is of ELF format and it's headers have information about its dependent shared libraries. This can be viewed by issuing the ldd &ltdynamic_lib&gt command on it. Also dynamic loader knows at runtime to pick the dependent libraries from the standard locations. If it can't find, it will give an error. In your case, you will find the information about the dependent libraries of your dynamic library in your ldd output and presumably, all those libraries will be in a standard location. That is the reason you are not finding an error, when you are trying to dynamically link.

Here are some useful links to know more about this,
https://renenyffenegger.ch/notes/development/dynamic-loader
https://amir.rachum.com/blog/2016/09/17/shared-libraries/

sanoj subran
  • 401
  • 4
  • 11
1

When you do a find_package in CMake will search for a Findxxx.cmake file in some CMAKE-defined paths.

The command has two modes by which it searches for packages: “Module” mode and “Config” mode. Module mode is available when the command is invoked with the above reduced signature. CMake searches for a file called Find.cmake in the CMAKE_MODULE_PATH followed by the CMake installation.

(https://cmake.org/cmake/help/latest/command/find_package.html)

So you have to define your own FindSDL2.cmake which will tell where is the library. (https://cmake.org/cmake/help/v3.17/manual/cmake-developer.7.html)

And you need to say to find_package to search for your own FindSDL2.cmake. You can pass a path to find_package to perform this.

If you make CMake use your file, the variables ${SDL2_INCLUDE_DIRS} and ${SDL2_LIBRARIES} you will be the one you have defined in your file.

  • Thanks for the reply! I undenstand find_package a bit more now. I'm positive it's using `/usr/local/Cellar/sdl2/2.0.12_1/lib/cmake/sdl2-config.cmake` to configure itself as a library. It looks like it's defining itself both as a STATIC and SHARED library, I'm even more confused now. Here are the contents of that file, https://gist.github.com/feresr/0c1c29fb2887a1d75bc4db7785e8231d – frankelot Apr 17 '20 at 16:30
  • By "defining itself both as STATIC AND SHARED" I mean -> these two lines are present add_library(SDL2::SDL2 SHARED IMPORTED) and add_library(SDL2::SDL2-static STATIC IMPORTED) are defined. – frankelot Apr 17 '20 at 16:33
  • Are you sure your CMakeLists it's using this .cmake ? Because it's not a FindSDL.cmake – Aurélien Foucault Apr 17 '20 at 16:38
  • I'm sure, as a quick test I added `message("this is being run")` to the sdl2-config.cmake file, and it was printed to the console. – frankelot Apr 17 '20 at 17:05
0

A couple of possibilities here ..

The easiest explanation is that you have a dynamic libpng.so.x installed somewhere where your dynamic linker can find it (/usr/lib, /usr/local/lib, etc), but don't have a static libpng.a. The answer in that case would be to install a static libpng.a and see if cmake's magic can resolve it.

The next easiest explanation is that there is no libpng.* on your system and there is some dynamic late binding going on. This can happen if, for instance, the software that you are building is also building libpng.* in which case the late binding will allow you to successfully dynamically link your application to it, even though libpng actually has not yet been installed in /usr/lib or /usr/local/lib or wherever the software you are building will eventually put it. This is sort of a chicken-and-egg situation. You can figure this out by issuing "ldd foo" where "foo" is the name of your application. The output will be a grocery list of all the things the dynamic linker needs to fetch for your executable, libpng should be in the list and there should be a resolved path listed for it, if not it's possible that your application may start but subsequently crash if for instance png_write_png() is at some point invoked. How to workaround this would be to install the libpng.* libraries that your application is expecting, prior to building.

Hope this helps a bit :)

Andrew Atrens
  • 509
  • 2
  • 5
  • Thanks! I've checked and `libpng.a` is already present in `/usr/local/lib/`. Please see my updated question above (my program runs OK if I add all dependencies explicitly) – frankelot Apr 22 '20 at 22:45