Exporting Symbols¶
The Arbor libraries are compiled with hidden visibility by
default which strips a compiled library of all symbols not explicitly marked as visible. Note that
the term visibility here refers to the symbol visibility exposed to the linker and not to language
access specifier such as public/private. Arbor provides a couple of macros to make functions and
classes visible which are defined in the header file export.hpp
in each library’s include
directory, i.e. include/arbor/export.hpp
. These header files are generated at configure time
based on the build variant, compiler and platform.
By default, hidden/visible symbols will affect shared libraries directly. Since the linker is
responsible for making symbols hidden, the visibility settings are not applied to static
libraries immediately, as static libraries are generated by bundling object files with the archiver
(standard in cmake for STATIC
libraries). In principle, the linker would be able to generate
relocatable output instead (ld -r
) by incrementally linking object files into one output file
(sometimes called partial linking) which would apply proper visibility to static libraries.
However, we currently do not handle this case in our build scripts as it is not not nativley
supported by cmake yet.
Note
When linking an application with static Arbor libraries the linker may issue warnings (particularly on macos). Thus, if you encounter problems, try building shared Arbor libraries (cmake option -DBUILD_SHARED_LIBS=ON
) instead.
Macro Description¶
-
ARB_LIBNAME_API¶
Here “
LIBNAME
” is a placeholder for the library’s name:ARB_ARBOR_API
for the core Arbor library,ARB_ARBORIO_API
for Arborio, etc. This macro is intended to annotate functions, classes and structs which need to be accessible when interfacing with the library. Note that it expands to different values when Arbor is being built vs. when Arbor is being used by an application.Below we list the places where the macro needs to be added or can be safely omitted (we assume all of the symbols below are part of the public/user-facing API). In general, annotation is required
for declarations in header files which are not definitions
for definitions of functions, friend functions and (extern) variables in source files
Members and member functions of already visible classes will be visible, as well, without further annotation, except for friend function (see below).
Implementation details and internal APIs may not need annotation as long as they do not require visibility across the library boundary (though some annotations are required for unit test purposes). Exception classes and type-erased objects need special annotation, see
ARB_SYMBOL_VISIBLE
.header.hpp¶#include <arbor/export.hpp> // free function declaration ARB_ARBOR_API void foo(); // free function definition inline void bar(int i) { /* ... */ } // function template definition template<typename T> void baz(T i) { /* ... */ } // class definition // class member declaration class ARB_ARBOR_API A { A(); friend std::ostream& operator<<(std::ostream& o, A const & a); }; // class definition // class member definition class B { B() { /* ... */ } }; // template class definition // class member definition template<typename T> class C { C() { /* ... */ } }; // (extern) variable declarations ARB_ARBOR_API int g; ARB_ARBOR_API extern int h;
source.cpp¶// free function definition ARB_ARBOR_API void foo() { /* ... */ } // class member definition (will be visible since A is visible) A::A() { /* ... */ } // friend function definition ARB_ARBOR_API std::ostream& operator<<(std::ostream& o, A const& a) { /* ... */ } // (extern) variable definitions ARB_ARBOR_API int g = 10; ARB_ARBOR_API int h = 11;
-
ARB_SYMBOL_VISIBLE¶
Objects which are type-erased and passed across the library boundaries sometimes need runtime type information (rtti). In particular, exception classes and classes stored in
std::any
or similar need to have the correct runtime information attached. Hidden visibility strips away this information which leads to all kind of unexpected behaviour. Therefore, all such classes must be annotated with this macro which guarantees that the symbol is always visible. This also applies for classes (or structs) which are entirely defined inline. Note, one must not useARB_LIBNAME_API
for these cases.header.hpp¶#include <arbor/export.hpp> // exception class defintion and class member definition class ARB_SYMBOL_VISIBLE some_error : public std::runtime_error { some_error() { /* ... */ } }; // class defintion and member defintion // class D will be type-erased and restored by an any_cast or similar class ARB_SYMBOL_VISIBLE D { D() { /* ... */ } };