padraic.xyz

Libraries

Libraries allow us to save time, both in terms of coding and in understanding algorithms for common techniques, so that we can focus on the problems in our domain instead. WEe have to pick carefully, including considering license etc, when choosing a library.

Linking Libraries

Consider a compiled class, built using a header file and an implementation file, that have been compiled down to an object file .o. We then 'link' to the .o file when compiling our main code.

But what if we have a large collection of object files to do different things? Then we compile them together in to a library, which contains a namespace, names, declarations and implementations.

There are two types of library

  1. Static Library:

    These are denoted by .a on Unix and .lib on Windows, typically given a name like libmycode.a.

  2. Dynamic Library:

    These are denoted by .dylib on osx, .so on linux and .dll on windows.

How do we consume the library? In our main.cpp file, we first need to add the preprocessor instruction to include the library header file, which contains declarations of the library contents. When compiling, we then have to link to the library.

For static libraries, the resulting binary copies in the entire contents of the included library. If we use exclusively static libraries, our programme has no external dependencies. Note that the same concept applies if we are compiling a library which includes a static library.

For dynamic libraries, the compiled code is 'left' in the library. At runtime, the OS then checks for the dependency library in known locations. If it isn't found, the programme will not execute! This allows us to save disk-space, as the library needs to be installed only once and each programme can link to it, and the resulting programme binaries are smaller.

Aside: We can run in to isues if our code is compiled with different 'versions' of te standard library (namely a different major or minor compiler version). This is a concern to be aware of when relying on dynamic library. This is especially irritating on windows if we are in debug or release mode.

We can use command-line programmes like otool (OSX), ldd (Linux) or the programme Dependency Walker (Windows) to explore what dependencies our binaries have.

We also note that there is a separate method called Dynamic Loading, which is normally used for plugins. In this case, the command dlopen can dynamically discover function names and variables found in a file.

There is also a license consideration: statically linking classifies your work as a 'derivative work', and so if using the LGPL you should use dynamic linking.

Finding Libraries

Package managers, which handle dependencies and 'recomennded' versions, make installation of software or libraries significantly easier. This works fine UNTIL you run in to a need to up/downgrade to a different version. Some additional considerations are discussed in the notes, where we focus on 'standard build' and 'superbuild' methods. The former sets up the project to know the locations of dependencies, the latter fetches and builds everything itself.

There are flags in CMake which control this behaviour. In particular, the flag build shared libs will do a fully statically linked version.

Using the Library

Consider the 'fundamental' method, directly calling g++. The first step is to compile the main.cpp, and passing a directory for the compiler to look for libraries with the flag -I. The second step is to compile the binary form the .o file, additionally specifying a directory with libraries using -L, and the library name with -l. :::shell g++ -c -I/users/padraic/myproject/include main.cpp g++ -o main main.o -L/user/padraic/myproject/lib -lmylib

Using CMake:

When specifying headers of libraries, then we add a corresponding entry to CMakeLists.txt to add the directory to the list of include directories, for example:

include_directories(${CMAKE_SOURCE_DIR}/Code/)
add_subdirectory(Code)
if(BUILD_TESTING)
  include_directories(${CMAKE_SOURCE_DIR}/Testing/)
  add_subdirectory(Testing)
endif()

Another option when using external libraries is to either again manually specify directories, or to define a find_package method. As an example,

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS}) 
list(APPEND ALL_THIRD_PARTY_LIBRARIES ${OpenCV_LIBS})
add_definitions(-DBUILD_OpenCV)

which leverages the modules contained in CMake that define how to find certain packages on the current platform. These are difficult to create yourself, so you should check with CMake, or the developer, or the community, to find options on loading these libraries, or go for the super-build pattern. We can add CMake modules by appending to CMAKE_MODULE_PATH.

If the resulting package is found, a variable is added to the CMake Cache called {PACKAGENAME}_FOUND, set to true, and he necessary variables will be set. Otherwise, it will either continue on if the package was optional or exit. If not found, we can then configure the install manually using ccmake.

We note that when defining our own CMake modules, we can do a number of things including forcing the programme to download and compile a set version, and then setting additional preprocessor flags that we can then reference back in our main.cpp.