Dynamic Library
Let's now turn our static library into a dynamic library. It is a bit trickier with dynamic libraries since several things should be tweaked. First, we need to be able to mark methods (or classes) in our library as exported. Second, we need to tell our application where to look for our library at run time using rpaths.
Some compilers, like MSVC, require us to mark which symbols we want to export or import. The canonical way would be to define some helper macros. Let's start with a header that defines those helper macros:
#ifndef LIB_GLOBAL_H
#define LIB_GLOBAL_H
#if defined(_WIN32) || defined(WIN32)
#define MYLIB_DECL_EXPORT __declspec(dllexport)
#define MYLIB_DECL_IMPORT __declspec(dllimport)
#else
#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
#endif
#if defined(MYLIB_LIBRARY)
#define MYLIB_EXPORT MYLIB_DECL_EXPORT
#else
#define MYLIB_EXPORT MYLIB_DECL_IMPORT
#endif
#endif // LIB_GLOBAL_H
MYLIB_EXPORT
macro that expands either to an "export" or to an
"import" directive based on the presence of the MYLIB_LIBRARY
macro. We can use this macro
to mark a function as follows:
#include "lib_global.h"
MYLIB_EXPORT const char *get_string();
DynamicLibrary {
name: "mylib"
files: [
"lib.c",
"lib.h",
"lib_global.h",
]
version: "1.0.0"
install: true
Depends { name: "cpp" }
cpp.defines: ["MYLIB_LIBRARY", "CRUCIAL_DEFINE"]
cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
Export {
Depends { name: "cpp" }
cpp.includePaths: [exportingProduct.sourceDirectory]
}
Depends { name: "bundle" }
bundle.isBundle: false
}
MYLIB_LIBRARY
to the list of
defines. We do this only when building the library but we are not exporting
it – that way the users of our library will have methods marked for import rather than export.
Finally, we set cpp.sonamePrefix to "@rpath"
. This is required only
for Apple platforms, see
Run-Path Dependent Libraries
for details.
It is also required to set cpp.rpaths in our application file. Since the
library is installed to the lib
directory and the application is installed to the bin
directory, we need to tell the loader to look in the lib
directory. The
FileInfo.relativePath method can help us:
cpp.rpaths: {
if (!cpp.rpathOrigin)
return [];
return [
FileInfo.joinPaths(
cpp.rpathOrigin,
FileInfo.relativePath(
FileInfo.joinPaths("/", product.installDir),
FileInfo.joinPaths("/", "lib")))
];
}
cpp.rpaths: ["$ORIGIN/../lib"]
.
Don't forget to import qbs.FileInfo
in order to be able to use the
FileInfo extension.
To make the example complete, here's how the full app/app.qbs
file should look like:
import qbs.FileInfo
CppApplication {
Depends { name: "mylib" }
name: "My Application"
targetName: "myapp"
files: "main.c"
version: "1.0.0"
consoleApplication: true
install: true
cpp.rpaths: {
if (!cpp.rpathOrigin)
return [];
return [
FileInfo.joinPaths(
cpp.rpathOrigin,
FileInfo.relativePath(
FileInfo.joinPaths("/", product.installDir),
FileInfo.joinPaths("/", "lib")))
];
}
}