The Arbor libraries are compiled with `hidden visibility <https://gcc.gnu.org/wiki/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 <https://gitlab.kitware.com/cmake/cmake/-/issues/16977>`_.
.. 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 Descripiton
-----------------
.. c:macro:: 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
:c:macro:`ARB_SYMBOL_VISIBLE`.
.. code-block:: cpp
:caption: 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;
.. code-block:: cpp
:caption: 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;
.. c:macro:: 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 use
:c:macro:`ARB_LIBNAME_API` for these cases.
.. code-block:: cpp
:caption: 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