With a bit of care, it's possible to use dlopen
with shared libraries linked to the application.
dlopen
attempts to return the handle for an already loaded shared object. As long as the shared object is correctly named, if it is loaded because of a linked symbol, the use of dlopen
will not cause it to be loaded a second time.
An example may make this more clear.
Here are three almost identical libraries. Each one defines a function called f
and another function with a unique name. (They also have non-exported initializers, which I added so that loading is visible.):
file f1.c
#include <stdio.h>
int f() { return puts("I'm f1::f"); }
int f1_g() { return puts("I'm f1_g"); }
static void init() __attribute__((constructor));
void init() { puts("Initializing f1"); }
file f2.c
#include <stdio.h>
int f() { return puts("I'm f2::f"); }
int f2_g() { return puts("I'm f2_g"); }
static void init() __attribute__((constructor));
void init() { puts("Initializing f2"); }
file f3.c
#include <stdio.h>
int f() { return puts("I'm f3::f"); }
int f3_g() { return puts("I'm f3_g"); }
static void init() __attribute__((constructor));
void init() { puts("Initializing f3"); }
These are compiled into shared objects, specifying the soname of each shared object:
for i in f1 f2 f3; do
gcc -Wall --shared -Wl,--soname=lib$i.so -fPIC -o lib$i.so $i.c
done
Here's a main function which explicitly refers to two of the unique function names, but relies on dlopen/dlsym to use the functions with common names. (It refers to two unique names rather than three so as to show the difference in loading behaviours, which is nonetheless transparent). Note that the library names being loaded with dlopen do not contain slashes, so they will match with an already loaded library with the same soname.
file main.c
#include <stdio.h>
#include <dlfcn.h>
extern int f1_g();
extern int f2_g();
extern int f3_g();
int (*f1_f)();
int (*f2_f)();
int (*f3_f)();
int main() {
puts("Loading f1");
f1_f = dlsym(dlopen("libf1.so", RTLD_NOW), "f");
puts("Loading f2");
f2_f = dlsym(dlopen("libf2.so", RTLD_NOW), "f");
puts("Loading f3");
f3_f = dlsym(dlopen("libf3.so", RTLD_NOW), "f");
f1_f();
f1_g();
f2_f();
f2_g();
f3_f();
// f3_g(); /* We don't explicitly use f3_g, so f3.so is not linked */
return 0;
}
Now, we compile and run it:
$ # Need to specify -L in the build and LD_LIBRARY_PATH when running
$ # because . is not in the default library search path.
$ gcc -o main -Wall main.c -L. -lf1 -lf2 -lf3 -ldl
$ LD_LIBRARY_PATH=. ./main
Initializing f2
Initializing f1
Loading f1
Loading f2
Loading f3
Initializing f3
I'm f1::f
I'm f1_g
I'm f2::f
I'm f2_g
I'm f3::f
So, f1
and f2
are loaded (and initialized) before main
starts because they are needed by explicitly used functions f1_g
and f2_g
. When a handle for these shared objects is requested (using dlopen
), the libraries are not loaded a second time. However, the call to dlopen
for libf3.so
does not find the shared object in the executable, so it is loaded (and initialized) by the call to dlopen
.
Subsequently, all of the functions are called and the expected versions of the various f
functions are obtained.