The shared objects technology is used, among other things, to provide the so-called “plug-in system”, that allows us to link in compiled code at runtime providing (eventually optional) features.
To implement plug-in systems, you usually need to call the dynamic linker at runtime to ask it to load the plug-in's shared object. This object might just be a standard shared object or might require further details to be taken into consideration.
The call into the dynamic linker also varies for what concerns
interface and implementation. Since most Unix-like systems provide
this interface through the dlopen()
function,
which is pretty much identical among them, lots of software relies
on just this interface, and leaves to
libtool the task of building the
plugins.
Software that is interested in wider portability among different operating systems will be interested instead in using the wrapper library and interface called libltdl.
Because of the wide adoption of libltdl in many types of applications, its support in autotools is available with great flexibility. This is because its wrapping abilities can easily be used on systems where libtool proper is not usually installed, and thus it's often convenient to have a local copy of it.
But with bundling libraries, problems ensue, and it can especially be a problem to choose between bundling a local copy of the library or just using the system one. The macros provided by libtool, even the recent version 2, support three styles for bundling libltdl: sub-configured directory, non-recursive inline build, or finally recursive inline build.
As well as these three options, there is also the more “standard” option of simply requesting the presence of the library in the system, as is done for any other dependency and checking for it. This method is neither assisted nor well-documented by the libtool manual and is thus rarely used.
For all three bundling styles as provided by
libtool, the reference macros in
the configure.ac
file are
LT_CONFIG_LTDL_DIR
and
LTDL_INIT
. When using the sub-configured option,
these two are the only two calls that you need. When using the
inline build, you need some extra calls.
Example 3.2. Buildsystem changes for bundled libltdl
In configure.ac
, for the various cases, commented
# automake needed when not using sub-configured libltdl # subdir-objects only needed when using non-recursive inline build AM_INIT_AUTOMAKE([subdir-objects]) # the inline build *requires* the configure header, although the name # is not really important AC_CONFIG_HEADERS([config.h]) # the inline build *requires* libtool with dlopen support LT_INIT([dlopen]) # find the libltdl sources in the libltdl sub-directory LT_CONFIG_LTDL_DIR([libltdl]) # only for the recursive case AC_CONFIG_FILES([libltdl/Makefile]) # choose one LTDL_INIT([subproject]) LTDL_INIT([recursive]) LTDL_INIT([nonrecursive])
The changes for Makefile.am
(or
equivalent) are trivial for the sub-configured and recursive
options (just add the new directory to
SUBDIRS
), but are a bit more complicated
for the non-recursive case. The following is a snippet from
the libtool manual to support
non-recursive libltdl inline builds.
AM_CPPFLAGS = AM_LDFLAGS = BUILT_SOURCES = EXTRA_DIST = CLEANFILES = MOSTLYCLEANFILES = include_HEADERS = noinst_LTLIBRARIES = lib_LTLIBRARIES = EXTRA_LTLIBRARIES = include libltdl/Makefile.inc
Whatever option you choose to follow at this point, you must
actually bundle the sources in your tree. You probably
don't want to add them to your source control system, but you want to add
the libtoolize --ltdl command to your
autogen.sh
script or similar.
As the title of this section suggests, you can technically even install the libltdl that you just built. This is not enabled by default, and rightly so (you'd be installing unrequired software outside of the scope of the build process). The reason why this is at all possible is that the macros used by the libtool package are exactly the same as is provided to third-party developers.
Finally there is no provided macro to check for the library in the system to
rely on; since it also does not provide a pkg-config datafile. The
best practices choice is simply to discover the
library through AC_CHECK_LIB
.
To do that you can use the following snippet of code, for instance:
Example 3.3. Checking for libltdl
AC_CHECK_HEADER([ltdl.h], [AC_CHECK_LIB([ltdl], [lt_dladvise_init], [LIBLTDL=-lltdl], [LIBLTDL=])], [LIBLTDL=])
It's important to check for a function that is present in
the currently supported version of
libltdl. This snippet checks for
the lt_dladvise_init
function that is a
new interface present in libtool 2.2 and later.
When building plug-ins that are to be used directly with the
dlopen()
interface (or equivalent) and not
through the libltdl interface, you
usually just need the shared object files, without versioning or
other frills. In particular, given the plug-ins cannot be
wrapped statically, you don't need to build the static version
at all.
For this reason when building these very 'casual' types of plug-ins, we just rely on three flags for the libtool script:
-module
Ignore the restriction about the lib-
prefix for the plug-in file name, allowing free-form
names.
-avoid-version
Allow the target to not provide any version information, removing the need to provide it. Almost all the plug-in systems don't use the library version to decide whether to load the objects, and rely instead on the path they find.
-shared
Disable entirely the build of the static version of the object, this reduces the number of installed files, as well as avoiding the double-build that would be needed for all the systems where static libraries and shared objects have different build requirements.
This option will make the package incompatible with the
--disable-shared
option during
./configure call, as well as stopping
the build when shared objects are not supported at all.
-export-dynamic
The current object's exposed symbols have to be accessible
through dlsym()
or equivalent
interfaces.
Example 3.4. Building plug-ins for dlopen()
usage
pkglib_LTLIBRARIES = foo_example_plugin.la foo_example_plugin_la_SOURCES = example.c foo_example_plugin_la_LDFLAGS = -avoid-version -module -shared -export-dynamic
When designing plugin interfaces you have two main choices available: either you use a fixed interface or a variable one. In the former case, all plugins export a set of symbols with a pre-selected name, independent of the names of the plugins themselves. With the latter option, the symbols exported by each plugin are instead named after the plugin. Both alternatives have up- and downsides, but these are another topic altogether.
For instance, a fixed interface can consist of three functions
plugin_init()
, plugin_open()
and the
plugin_close()
, that need to be implemented by each plugin. On the other
hand, in the case of a variable interface, the foo plugin would export
foo_init()
, foo_open()
and the
foo_close()
.
Depending on which of the two alternative solutions is chosen, you have alternative approaches
to tell the link editor to only show the interface symbols and nothing else, as delineated in
Section 3.2, “-export-symbols
and -export-symbols-regex
”, and exemplified below.
Example 3.5. Exposing symbols for plugins with fixed interface
Since each plugin with a fixed interface exports the same set of symbols, and such interface
is rarely extended or reduced, the easiest solution here is to use the static
-export-symbols
option with a fixed list of symbols:
AM_LDFLAGS = -avoid-version -module -shared -export-dynamic \ -export-symbols $(srcdir)/plugins.syms pkglib_LTLIBRARIES = foo.la bar.la baz.la foo_SOURCES = foo1.c foo2.c foo3.c bar_SOURCES = bar1.c bar2.c bar3.c baz_SOURCES = baz1.c baz2.c baz3.c
Example 3.6. Exposing symbols for plugins with variable interface
When the interface is variable, it is common to have either a prefix or a suffix with the
name of the plugin itself; you could then use a generic list of symbols and produce its
specific symbol list, or you can make use of -export-symbols-regex
with a
wider match on the interface.
One of the downsides of using this method is that you then have to carefully name the functions within the plugin's translation units, but the build system should not be used to add workarounds for badly written code.
AM_LDFLAGS = -avoid-version -module -shared -export-dynamic \ -export-symbols-regex '^([a-zA-Z0-9]+)_(init|open|close)$$' pkglib_LTLIBRARIES = foo.la bar.la baz.la foo_SOURCES = foo1.c foo2.c foo3.c bar_SOURCES = bar1.c bar2.c bar3.c baz_SOURCES = baz1.c baz2.c baz3.c