Creating a C Shared Library with CMake

Iacopo Palazzi
6 min readOct 24, 2020

--

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

--

--

Iacopo Palazzi
Iacopo Palazzi

Written by Iacopo Palazzi

IoT Engineer working in AWS Professional Services, passionate about Software Development and DevOps.