Search code examples
c++pluginsshared-librariesubuntu-16.04dlopen

What is the proper way to reference shared library plugins with dlopen()?


I've got a project, called Super in this hypothetical example, with a set of .so files that are built into /home/whatever/super/. At runtime, a specification configuration file tells Super which plugins to load using their .so name. This is on Ubuntu 16.04.

Example plugins:

  • /home/whatever/super/magic.so
  • /home/whatever/super/wow.so
  • /home/whatever/super/awesome.so

I've set LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=/home/whatever/super

Within Super, I load the modules using dlopen():

std::string filename = "magic.so"
dlopen(filename.c_str(), RTLD_LAZY)

At this point, everything works. Now I'm trying to package my project and that means moving things to the correct system directories. I've now switched to using /usr/lib/x86_64-linux-gnu/super/ as the base path of the plugins, like so:

  • /usr/lib/x86_64-linux-gnu/super/magic.so
  • /usr/lib/x86_64-linux-gnu/super/wow.so
  • /usr/lib/x86_64-linux-gnu/super/awesome.so

I've also cleared LD_LIBRARY_PATH. I updated the dlopen() code to look like this:

std::string filename = "super/magic.so"
dlopen(filename.c_str(), RTLD_LAZY)

Unfortunately, the system won't load my module. I get this error:

Cannot load library: super/magic.so: cannot open shared object file: No such file or directory

I confirmed that /etc/ld.so.conf.d/x86_64-linux-gnu.conf exists and contains:

# Multiarch support
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu

What am I doing wrong? I've confirmed that /usr/lib/x86_64-linux-gnu/super/magic.so exists. Why is the loader not searching for the .so file in that subdirectory of /usr/lib/x86_64-linux-gnu? I obviously don't want to hard-code the full paths of the .so files into my C++ code.

Finally, is this a good approach for where to put the plugins and the loading approach?


Solution

  • It looks like the common approach is to encode the full paths to the plugin directory (or the plugin .so files themselves), then store that path either at compile-time or runtime, rather than relying on the LD components to find the subdirectory.

    Compile-Time

    You can use the CMake process to determine the installation directory of the plugin modules, and write that directory into a file prior to compilation, as in Remmina:

    Line within config.h.in:

    #define REMMINA_PLUGINDIR   "${REMMINA_PLUGINDIR}"
    

    Lines within CMakeLists.txt:

    if(NOT REMMINA_PLUGINDIR)
        set(REMMINA_PLUGINDIR "${CMAKE_INSTALL_FULL_LIBDIR}/remmina/plugins")
    endif()
    
    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h)
    

    Runtime

    For a runtime solution, CMake can write the installation path into a config file that is installed along with the software. For example:

    Line within super.conf.in:

    PLUGIN_PATH "@SUPER_PLUGINDIR@"
    

    Lines within CMakeLists.txt:

    if(NOT SUPER_PLUGINDIR)
        set(SUPER_PLUGINDIR "${CMAKE_INSTALL_FULL_LIBDIR}/super")
    endif()
    
    configure_file(
        ${CMAKE_CURRENT_SOURCE_DIR}/super.conf.in
        ${CMAKE_CURRENT_BINARY_DIR}/super.conf
    @ONLY)
    
    install(
        FILES ${CMAKE_CURRENT_BINARY_DIR}/super.conf
        DESTINATION ${CMAKE_INSTALL_LIBDIR}
    )
    

    On my Ubuntu 16.04 system, CMAKE_INSTALL_LIBDIR is an alias for lib/x86_64-linux-gnu and CMAKE_INSTALL_FULL_LIBDIR is an alias for /usr/lib/x86_64-linux-gnu when creating a package that installs at the system root.

    Either way, you can then build a full path with dlopen that doesn't require an LD search. Just concatenate the configured string to the plugin filename. The two CMake variable substitution approaches are not critical. I just threw in the ${} and @@ alternatives for completeness.