In my previous post of plugins versus builtins I’ve given a few reasons to make use of plugins, explaining the bad side of them, which left up in the air the fact that you can very well decide to support both plugins and builtins – I think VLC does that by the way – and let the user (or the distributor) choose at build-time whether to link them in statically or link them externally.
To make this possible, you’ve obviously got to deal with a few different design decisions; while plugins have more or less the same basic idea for design, their implementation may be quite different. Since usually you need more than one function from a plugin (at least setup, action and teardown), you have different methods to handle them.
For instance, you can have one symbol per function, and use
dlopen() (or similar) for each of them to fill in a structure to call the various interfaces, or you can have a single structure already filled in with the pointers to the (static) functions, or finally you can have a single initialization function that fills in the structure and return it to the calling application.
While the difference between these approaches may seem to be minimal, it actually can be quite a difference: calling into the dynamic loader multiple times can waste time, so having a single entrypoint makes perfect sense from a design point of view. But the remaining two approaches are also very different.
From a logic point of view of a theoretical programmer, having a structure, an object, already pre-filled with the pointers of the static member functions is the best choice. This is, after all, how the C++ vtables work: they are little more than a structure with functions’ addresses. But if your background is more practical than theoretical, you might have already noticed a problem here; when using position independent code (PIC or PIE) all the objects containing pointers (doesn’t matter whether they point to functions or data) need to be relocated at runtime (that’s what
.data.rel is used for).
When relocating objects, the
.data.rel section of an executable object (shared or not), will become dirty, causing it not to be shared among processes any longer. As I’ve written in the previous post, prelink does not work on them which means it won’t be able to alleviate the effect of relocation on the object (like it would be for normal shared libraries). Also, since plug-ins are isolated shared objects, they don’t share the sections of the main program – I already said this before – the relocation of small “vtable-like” objects will cause the copy-on-write of a full page (4KiB) for each plugin loaded, even when the object itself is just a few bytes big.
My preferred solution is to have a single interface function, which, once called, will fill-in a heap-allocated function with the pointers to the rest of the interface function. This option allows to just have a single symbol per plugin that needs to be looked up, and at the same time sidesteps the problem of relocation (the pointers will be calculated at runtime, just like a relocation, but without the memory hit).
The next post in the series will try to focus more on the style of the initialisation function, to be shared between plugins and builtins.