Creating a C Shared Library with CMake
The purpose of this post is to give an example on how to create a C Shared library using CMake as building tool in a Linux box.
Why?
I couldn’t find a clear and simple example on how to do such a basic and quite common thing like creating a C library using CMake as building tool. This is not a guide on C language, not even a guide on what a C library is, this is more like a collection of findings needed in order to achieve this purpose.
Purpose
Generally speaking, it’s a good (required!) practice to write software following a modular design in order to make it easy to evolve/maintain/understand. Libraries are a way to isolate common pieces of software (let’s say functions), making them available to other programs, offering integration points (usually called APIs) that the applications will use in order to leverage the algorithms implemented through them.
Libraries might also be designed with the goal of splitting what will be exposed to the external world with the inner logic that will actually implement the logic. That’s the reason why I’ve designed the example with public and private structure:
public
: contains APIs definition that will be publicly accessible through.h
files.private
: contains the real implementation of the logic that will be hidden (not linkable), accessible only through public APIs.
Scenario
What the example aim to do is to create a library that implements internal basic math operations (sum, subtraction, multiplication, division) exposing only a single functionality (called compute) that will be externalized in order to let the actual program to use them.
Workspace
What we will build is a shared library in Linux that will be integrated by a program. The tools we need are:
- A Linux box (I’ve used Ubuntu 20.04 for this example, but should work in any Linux distro that supports the following tools).
- CMake version 3.4+
- Gcc 9+
- A text editor
Due to the simplicity of the example, older versions of the tools should work too.
The Code
The running code of the example can be found here.
What we want
So, here we are. We want to implement a library that will implement basic math operations:
- Sum
- Subtraction
- Multiplication
- Division
However, the purpose of our example is not only to implement the logic behind these operations, but also to decouple the specific operation implementation with a more generic and centralized single point of contact (external/public APIs).
That’s why we will expose to the program another function (public API):
- Compute
Compute will be integrated by the final program and will act as an intermediator (proxy) of the real operations’ implementation.
Code’s structure
Before going deeper in the code, let’s prepare a folder where to store our example like /tmp/example/
, the structure of our project will be like that:
/tmp/example
├── calc
│ ├── build
│ ├── CMakeLists.txt
│ └── code
│ └── main.c
└── library
├── build
├── CMakeLists.txt
└── code
├── private
│ ├── include
│ │ └── operations.h
│ └── src
│ └── operations.c
└── public
├── include
│ └── mymath.h
└── src
└── mymath.c
The two main folders, library
and calc
, will intuitively contain the library and the program that will use it.
Each folder contains a CMakeList.txt
file, a build
and code
folder. This is because:
CMakeList.txt
: contains the definition of the build process.code
: contains the source code (of library and program).build
: will be used for the out-of-source build approach. If you don’t find it in the repo, just create it by yourself.
A word should be spent on the out-of-source concept: CMake is a fantastic tool that hides a lot of complexity around the building processes (compilation, linking, installation, …). To achieve this, a lot of intermediate files are needed to CMake to orchestrate tasks. These files are not strictly part of the project you want to manage, but rather part of the tool-chain used to build it. For the sake of a clean environment, it’s a best practice to initialize the building process inside a different folder (build
) to avoid files mix-up and make it easy to clean up stuff (e.g. it’s easy to keep things clear in a git repo by putting it on .gitignore
).
Library
The code
folder division is something I’ve taken to the extreme in order to give a clear view of the concept of externalization. In the private
folder, I’ve put the code that handles the real implementation of the math operations, while the public
folder contains the API that will be exported in order to let other programs to use it.
In the public/include
one, we can find the header file mymath.h
that contains the signature of the API that will be used to trigger the inner logic defined into the file operations.h
located into the private/include
.
Private implementation
This is the content of the operations.h
header file:
Nothing difficult here, just a simple definition of functions accepting two doubles as inputs, returning a double as output. Let’s see the related implementation in the operations.c
file under private/src
folder:
Public implementation
This is the content of the mymath.h
:
There is something interesting here. At line 20, you can see a more “complex” function called compute
that takes two doubles as input (the operands of the operation) and an enum
that defines the operation to perform with them. The result is a struct containing an error flag and the value, which is the result of the operation performed.
This function is marked with the EXPORT
attribute that is resolved during compile time with __attribute__((__visibility__("default")))
directive, which instructs the linker that this symbol must be exported. This is the point where we choose to make the function visible in the final library’s binary in order to let other programs link to it.
So, let’s see the implementation inside mymath.c
:
It’s pretty clear how the private and public parts are connected through this implementation: the public function compute
uses the private operations sum, sub, mult, div
defined into the operations.h
header file, which implementation is into the operations.c
one.
Bulding everything
CMake will take care of organizing all the files shown above, creating build and link tasks through gcc
. This is the CMakeList.txt
:
This CMakeList.txt
file is quite simple, what it’s worth to be highlighted is on lines 46–48 (ADD_LIBRARY
), where we set the shared library definition. In lines 50–55 (SET_TARGET_PROPERTIES
) we define the PUBLIC_HEADER
parameter which declares what header files (.h) will be considered external (in the example, it’s mymath.h
). Those are the files that will be propagated within the system with the INSTALL
directive (see line 65), in order to let other programs include them in their source code at compile time, and linking against library .so
file at linking time.
To build everything, just place yourself into the build
directory and run:
/tmp/example/library/build $ cmake ..
This will procude building automation files used by make
. After this, just run:
/tmp/example/library/build $ make
This will actually build the library. What’s remaining is install it into the system:
/tmp/example/library/build $ sudo make install
That’s it, you should find mymath.h
under /usr/local/include
as well as libmymath.so
under /usr/local/lib
.
Using the library
Now that the library has been built and installed, using it is very easy. Just include the header file mymath.h
into your code and use the functionalities exposed by it. This is the code of the main.c
code that you can find under the code/src
path:
Also, the related CMakeLists.txt
:
The procedure used to build the example is similar to the library’s one. Place yourself into the build
directory and run:
/tmp/example/calc/build $ cmake ..
This will procuded building automation files used by make
. After this, just run:
/tmp/example/calc/build $ make
At the end of building process, you’ll find a calc
executable. Just run it:
/tmp/example/calc/build $ ./calc
Operation result: 0.500
Thanks
I hope this helps someone speeding up things, just let me know on comments :)
Ciao.
IP