A minimal host process + dlopen plugin + shared-library bridge setup, the pattern behind
plugin systems: the host does not know the plugin at link time and loads it at run time.
src/demo/dlopen/
├── CMakeLists.txt
├── bridge.h / bridge.cpp # SHARED library: API + one singleton for the whole process
├── sample_app.cpp # the "plugin": a SHARED library that contains main()
├── main.cpp # the host: dlopen + dlsym("main")
└── README.md
cmake --build build && ./build/bin/demo_dlopen
ctest --test-dir build -R demo:dlopen # the same as a testThe plugin path is compiled into the host (SAMPLE_APP_PATH, set by CMake), so the demo works
from any working directory. Pass another path as the first argument to load a different plugin.
Expected flow
demo_dlopenloadslibsample_app.sowithdlopen- it looks up the symbol
mainwithdlsym(C++ never mangles the namemain) - a background thread writes a register through the bridge and marks it ready
- the plugin waits for that signal, then writes and reads a fake register
- both sides use one bridge singleton, because
bridgeis a SHARED library
host --dlopen--> libsample_app.so --calls--> libbridge.so
| ^
+--------------------------------------------------+
same singleton
If bridge were a STATIC library, the host and the plugin would each get their own copy of the
singleton. bridge_wait_ready() in the plugin would then never see the host's ready flag and
would block forever.
Try the broken variant:
# in CMakeLists.txt, change:
add_library(bridge STATIC bridge.cpp)Rebuild and run: the plugin hangs in bridge_wait_ready() (stop it with Ctrl+C).
dlopenflags:RTLD_LAZYresolves symbols on first use (RTLD_NOWresolves everything at load time),RTLD_GLOBALmakes the plugin's symbols visible to libraries loaded later.dlerror()must be cleared beforedlsym, becausedlsymmay legitimately returnnullptr.- The handle is closed by a
std::unique_ptrwith a custom deleter - RAII also works for C APIs.