Understanding C++ Libraries and Linking : Static vs. Dynamic
A library is a fundamental concept in software development that allows code to be reused across multiple programs. It contains precompiled functions, classes, or other code components that can be linked into an executable program. Libraries can be divided into two main types: static libraries and dynamic libraries. In this article, we’ll explore the importance of libraries and walk through practical examples of creating and using both types in a C++ application. We’ll also learn how to view dependencies using tools like Dependency Walker.
Static library
A static library is a file that contains compiled code that can be linked directly into your application during compilation. When a program uses a function from a static library, the entire contents of the library are copied into the program’s executable file. This means that the resulting executable contains all the code it needs to run. Static libraries create larger binary files, and need more space on disk and main memory.
When you use a function from a static library, the compiler adds the code for that function to the executable file of your application. This results in a standalone executable, but can increase the size of the binary.
Let’s create a simple static library that contains a function to calculate the square of a number and another to display a message.
// LibraryStatic.h
#pragma once
int SquareStatic(int num);
void DisplayMessageStatic();
// LibraryStatic.cpp
#include "pch.h"
#include <iostream>
#include "LibraryStatic.h"
int SquareStatic(int num) {
return num * num;
}
void DisplayMessageStatic() {
std::cout << "Hello from the static library!" << std::endl;
}
Dynamic library
Dynamic libraries are separate files that are loaded into memory when a program starts running. Unlike static libraries, dynamic libraries are not included directly in the executable; they are loaded at runtime. They are linked during compile-time, but the actual code is loaded into memory at runtime. A dynamic library is essentially a shared object that can be loaded into memory dynamically during program execution. This approach reduces the size of the executable but requires the dynamic library file to be present on the system. Here are some key points about dynamic libraries:
- Runtime Resolution: When a program uses a function from a dynamic library, the function call is resolved at runtime, not during compilation. This means that the program doesn’t need to contain a duplicate copy of the function’s code, which saves space in the executable file.
- Version Flexibility: Dynamic libraries are particularly useful when a program needs to use the latest version of a function, even if newer versions become available after the program’s release.
Dynamic libraries are separate files that your application references rather than including their code directly. This approach results in smaller executables but requires the dynamic library file to be present at runtime
Let’s create a dynamic library with two functions: one to calculate the square of a number and another to display a message.
// LibraryDynamicGlobal.h
#ifdef LIBA_EXPORTS
#ifdef LIB_DYNAMIC_EXPORTS
#define LIB_DYNAMIC_API __declspec(dllexport)
#else
#define LIB_DYNAMIC_API __declspec(dllimport)
#endif
// LibraryDynamic.h
#include "LibraryDynamicGlobal.h"
int LIB_DYNAMIC_API SquareDynamic(int num);
void LIB_DYNAMIC_API DisplayMessageDynamic();
// LibraryDynamic.cpp
#include "pch.h"
#include <iostream>
#include "LibraryDynamic.h"
int SquareDynamic(int num) {
return num * num;
}
void DisplayMessageDynamic() {
std::cout << "Hello from the dynamic library!" << std::endl;
}
Linking Process
Linking process, managed by the linker during compilation, ensures that your code and libraries are combined correctly into an executable program. This linking process involves two main approaches: static linking and dynamic linking.
Static Linking (.lib
in Windows, .a
in Linux):
In static linking, the entire code of a static library is copied into the executable. This means that when you run the program, it doesn’t need the library file because all the required code is already included in the executable. However, this approach can make your executable larger, as it carries the code of all linked functions within itself. There’s no need for dynamic loading of external libraries during runtime because the code is already part of your program.
Dynamic Linking (.dll
in Windows, .so
in Linux):
Dynamic linking, on the other hand, follows a different approach. The executable contains references (symbols) to functions in a dynamic library, but not the actual code. The dynamic library file (commonly .dll in Windows or .so in Linux) must be present on the system at runtime. When you run the program, the operating system’s dynamic linker/loader locates and loads the necessary dynamic libraries into memory. This allows multiple programs to share the same library, reducing both disk space usage and memory overhead.
Using Both Libraries in an Application
Now, let’s create an application (main.cpp) that uses both the static and dynamic libraries:
#include <iostream>
#include "LibraryDynamic.h"
#include "LibraryStatic.h"
int main()
{
std::cout << "Hello from the application!" << std::endl;
int number = 5;
// Calling functions from the static library
DisplayMessageStatic();
int squaredStatic = SquareStatic(number);
std::cout << "The square of " << number << " is " << squaredStatic << std::endl;
// Calling functions from the dynamic library
DisplayMessageDynamic();
int squaredDynamic = SquareDynamic(number);
std::cout << "The square of " << number << " is " << squaredDynamic<< std::endl;
}
In this application, we include both LibraryStatic.h
and LibraryDynamic.h
to use functions from both libraries. The main.cpp
file demonstrates how to use functions from both the static and dynamic libraries within a single C++ application. When you compile and run this application, it will incorporate the static and dynamic libraries to perform calculations and display messages.
The output of the application will be as follows:
Hello from the application!
Hello from the static library!
The square of 5 is 25
Hello from the dynamic library!
The square of 5 is 25
Viewing Dependencies with Dependency Walker
Dependency Walker (http://www.dependencywalker.com), or similar dependency analysis tools (https://lucasg.github.io/Dependencies/), allows you to inspect the dependencies of your executable file (.exe). Here’s what you should see when using such a tool:
System Libraries: You may see dependencies on system libraries, which are part of the operating system. These are essential components that your application relies on to interact with the system.
Static Libraries: If you are using static libraries, you won’t typically see them listed as dependencies in Dependency Walker. This is because the code from static libraries is directly integrated into your executable during the compilation process. Static libraries become part of your application, so they don’t appear as separate dependencies.
Dynamic Libraries: If you are using dynamic libraries, you will see them listed as dependencies. These libraries are not included directly in your executable but are loaded at runtime. Your application relies on these libraries to provide certain functions or services.
Redistributable Packages: In some cases, you might see dependencies on redistributable packages, which are runtime libraries that are necessary for your application to run on other systems. These packages ensure that your application has access to the required libraries on target machines. An example on Windows is the Microsoft Visual C++ Redistributable Package.
Missing or Incompatible Dependencies: If any dependencies are missing or incompatible, Dependency Walker or similar tools will flag them as errors.
You can access the complete source code for the examples in this article on my GitHub repository.