Development/Tutorials/Creating Libraries
Development/Tutorials/Creating Libraries
Languages: عربي | Asturianu | Català | Česky | Kaszëbsczi | Dansk | Deutsch | English | Esperanto | Español | Eesti | فارسی | Suomi | Français | Galego | Italiano | 日本語 | 한국어 | Norwegian | Polski | Português Brasileiro | Română | Русский | Svenska | Slovenčina | Slovenščina | српски | Türkçe | Tiếng Việt | Українська | 简体中文 | 繁體中文
Abstract
If a part of your code could be used by more than one software module, like other programs or plugins, you should put that part in a shared library. This tutorial tells how to add the library to the buildsystem and how to prepare the source code.
Separating the code
It is good practise to put all source files for a library in a separate directory.
This tutorial assumes you want to create a shared library named myshare, which contains the classes MyAClass and MyBClass. Both classes use internally the classes MyInternalCClass and MyInternalDClass, but these are not found in their public interfaces.
The classes are declared and defined in the files myaclass.h, myaclass.cpp, mybclass.h, mybclass.cpp, myinternalcclass.h, myinternalcclass.cpp, myinternaldclass.h, and myinternaldclass.cpp.
Adding the library to the buildsystem
You also need a file CMakeLists.txt in the same directory as the source files:
include_directories(
- any directories needed by the sources of the library
)
set( myshared_LIB_SRCS
myaclass.cpp
mybclass.cpp
myinternalcclass.cpp
myinternaldclass.cpp
)
kde4_add_library( myshared SHARED ${myshared_LIB_SRCS} )
target_link_libraries( myshared
${KDE4_KDECORE_LIBS}
)
set_target_properties( myshared
PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}
)
install( TARGETS myshared ${INSTALL_TARGETS_DEFAULT_ARGS} )
The instructions are similar to the ones for a program. But instead of calling kde4_add_executable() you use kde4_add_library() to register the library myshared. The parameter SHARED is needed as this declares the library is a shared one, not a static.
Like with a program, other libraries that are used have to be defined with target_link_libraries().
The version number of the library is set by set_target_properties().
And install() moves the library to a place where it can be loaded.
Declaring exported classes and methods
If a library gets compiled, all functions or class methods it offers get listed as so called symbols in a table. Each symbol points to the offset in the library where the corresponding code can be found.
Adding also functions or class methods to the table which are not intended to be called from the outside is of no use. It just bloats the table and asks others to make calls to them against your will. For the library myshared you do not want to add the methods of the classes MyInternalCClass and MyInternalDClass.
In the KDE buildsystem you need to put explicitly a tag to all the classes and functions which the library should provide externally. This tag has to have a different name for each library.
For this add a file myshared_export.h to the directory of the library, containing:
- ifndef MYSHARED_EXPORT_H
- define MYSHARED_EXPORT_H
// needed for KDE_EXPORT and KDE_IMPORT macros
- include <kdemacros.h>
- ifndef MYSHARED_EXPORT
- if defined(MAKE_MYSHARED_LIB)
// We are building this library
- define MYSHARED_EXPORT KDE_EXPORT
- else
// We are using this library
- define MYSHARED_EXPORT KDE_IMPORT
- endif
- endif
- ifndef MYSHARED_EXPORT_DEPRECATED
- define MYSHARED_EXPORT_DEPRECATED KDE_DEPRECATED MYSHARED_EXPORT
- endif
- endif
This macro code defines the tag MYSHARED_EXPORT for the library.
Now you prepare all classes by adding a include for the header with the library's export tag and putting the export tag defined above in front of the class name, like this:
- include "myshared_export.h"
class MYSHARED_EXPORT MyAClass {
// class declaration as usual
};
And the same with the other class:
- include "myshared_export.h"
class MYSHARED_EXPORT MyBClass {
// class declaration as usual
};
Enabling others to compile using your library
You need also tell the buildsystem to install the header files. Without this external code can not build, as it can not include the files and thus the class and methods declarations of your library. You do this by adding to the library's CMakeLists.txt:
set( myshared_LIB_HDRS
myshared_export.h
myaclass.h
mybclass.h
)
install( FILES ${myshared_LIB_HDRS}
DESTINATION ${INCLUDE_INSTALL_DIR}/myshared
COMPONENT Devel
)
Of course also myshared_export.h is installed, as it gets included by the headers of both classes.
External KDE code which should use the library's classes and functions now can include the headers by the lines:
- include <myshared/myaclass.h>
- include <myshared/mybclass.h>
Getting the version numbers right
By installing the header files you are releasing your library officially. External code will rely on the interfaces declared by the headers. So you need to care for binary and behaviour compatibility if developing your library further. Any changes in the next release should be reflected in the version number, so external code gets compiled, linked and run with the matching version of your library.
As you already saw above the version of your library is defined by set_target_properties():
set_target_properties( myshared
PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}
)
So there are two different kind of versions, the development version VERSION and the ABI version SOVERSION. Both will be explained now.
Defining the development version
The value of VERSION defines the development state of your library. It is good practice to use a pattern "major.minor.patch-level". For each release you need to update or reset the different parts:
- Only bug fixes added: increase the patch-level, e.g. 2.3.10 -> 2.3.11
- New functionality added, while keeping the old functionality: increase the minor number, reset the patch-level, e.g. 2.3.10 -> 2.4.0
- Binary or behaviour changes of old functionality, regardless of any new functionality: increase the major number, reset the minor number and the patch-level, e.g. 2.3.10 -> 3.0.0
This version ends in the name of the resulting library file, e.g. on Linux "libmyshared.so.2.3.10" or "libmyshared.2.3.10.so".
Defining the ABI version
The value of SOVERSION defines the ABI variant of your library. It is a single always increasing integer, numbering the versions with non-backward-compatibel changes. So if you release a new version with some binary or behaviour changes you need to increase this version number by one, e.g. 2->3, otherwise you keep it.
This version ends in a reference with ABI versioning to the resulting library file, e.g. on Linux in the symbolic link "libmyshared.so.2", which points to the real library file. These versioned references are used by the dynamic linker to load the version of the library with the matching ABI variant to a running executable. Executables which are linked to a shared library have a note which ABI variant of these libray is needed.
Because conforming to the concept the older versions have the same ABI version like the newer one (e.g. 2). So if only an older version of the library is installed (e.g. 2.1.8) the executable is still started as the tested prerequisites (ABI version) are matched, but it will crash due to the missing functionality if the library gets finally loaded.
In a compilation-from-sourcecode-based system this just should not happen, unless you downgrade a library again. And in a package-based system dependency checks on de-/installations of packages which also care for the VERSION version should prevent this. So you might also forget about this problem.
Using the generic versions
If you are developing your library inside a normal KDE module, e.g. kdeutils, and all new versions of your library are only released together with the usual KDE releases, of course following the policies regarding ABI as demanded, you do not need to take care of the versioning yourself. Just use the generic variables ${GENERIC_LIB_VERSION} and ${GENERIC_LIB_SOVERSION} as shown above. Using the generic version numbers also helps to identify a version of your library by the general KDE version.
E.g. for KDE 4.1.0 these variables are defined in kdelibs/cmake/modules/KDE4Defaults.cmake with
set(GENERIC_LIB_VERSION "4.1.0")
set(GENERIC_LIB_SOVERSION "4")
Organizing your program code with static libraries
Sharing code with others is one reason to separate code into libraries. Another reason is the need to organize a large code base.
A useful approach to give a structure to code is to separate the source files in different subdirectories, each subdirectory containing only code for a certain domain, e.g. a subdirectory "utils" for all utility classes or "gui" for all gui related code.
Instead of one big central CMakeLists.txt file you also use an own CMakeLists.txt file per subdirectory, which is also placed in that subdirectory. In each of these CMakeLists.txt files you advise the build system to construct a separate package of the compiled sources in the given directory, a so called convenience library or, in technical terms, static library.
E.g. for the subdirectory "myutils" "myutils/CMakeLists.txt" looks like:
include_directories(
- only as needed for the sources of this directory/library
)
set( myutils_LIB_SRCS
myutilaclass.cpp
myutilbclass.cpp
myinternalutilcclass.cpp
myinternalutildclass.cpp
)
kde4_add_library( myutils STATIC ${myutils_LIB_SRCS} )
The instructions are similar to the ones for a shared library. In the call kde4_add_library() you just use the parameter STATIC instead to declare the library is a static one.
This library is only used as an intermediate product in the total build process. So you neither need to set a version number with set_target_properties() or declare at this point other libraries to link against with target_link_libraries(), like you do with a shared library. And you do not install anything, neither the lib or any header files. You also do not mark exported classes or functions, all will be available to external code. You declare any dynamic libraries used by the library at another place, see below.
With include_directories() you also have to list the include directories needed for the source files in the directory, and only these. So you get more control with regard to dependencies than with one big central CMakeLists.txt file.
To have all the local CMakeLists.txt files get used by the build system you need to add each subdirectory to the CMakeLists.txt file in the upper directory, with the call add_subdirectory(), e.g.
add_subdirectory( myutils )
add_subdirectory( mygui )
This inclusion can be done recursively, so in a subdirectory you can add again the subsubdirectory.
Finally you need to tell the buildsystem to also use the static libraries to construct the complete program. You do so by including their names in the target_link_libraries() call for the program. There you also add any not already listed shared library which is used by code in your static libraries, as told above. So to construct the program myprogram using the static libraries mygui and myutils you write the CMakeLists.txt like e.g.:
target_link_libraries( myprogram
mygui
myutils
- and all needed dynamic libraries, like the KDE ones and else,
- also the ones needed by your static libraries mygui and myutils
)
Make sure that you list those libraries, static or shared, which are also used by other libraries in the list below these, e.g. if mygui uses myutils, first list mygui, then myutils, as in the example above.
It is not uncommon for a static library to become a shared dynamic library later in development, if the code in the library proves to be complete and useful for others.