1

Using GCC and being on Linux, or probably wherever glibc is available, I can use the dl_open() library function to dynamically load a shared-object/DLL:

void *dlopen(const char *filename, int flags);

... and this also runs all functions which, in the ELF format, are marked with .init; or in the C/C++ code, are marked with __attribute__((constructor)) :

How exactly does __attribute__((constructor)) work?

My question is: How can I do the same in a more portable way? I'm interested in portability both to other compilers and other platforms.

Note: I marked this C++ because that's what I'm using, but obviously a C-ish solution in acceptable - as what I've described above is a C-ish solution.

einpoklum
  • 102,731
  • 48
  • 279
  • 553
  • 1
    boost has [dll module](https://www.boost.org/doc/libs/1_64_0/doc/html/boost_dll.html) but I am not sure if it fits your "portability". – Louis Go Aug 07 '20 at 07:22
  • @LouisGo: It's something. Care to make it into an answer? It would be even better if you could describe how it works on different platforms. – einpoklum Aug 07 '20 at 12:06

1 Answers1

3

Since Boost is available on many platforms, its _dll module might be considered as cross platform. Though compilation for different platforms are required.

Following is the basic example for Boost.Dll by importing a single variable in plugin.

Header

#include <boost/config.hpp>
#include <string>

class BOOST_SYMBOL_VISIBLE my_plugin_api {
public:
   virtual std::string name() const = 0;
   virtual float calculate(float x, float y) = 0;

   virtual ~my_plugin_api() {}
};

Source

#include <boost/config.hpp> // for BOOST_SYMBOL_EXPORT
#include "../tutorial_common/my_plugin_api.hpp"

namespace my_namespace {

class my_plugin_sum : public my_plugin_api {
public:
    my_plugin_sum() {
        std::cout << "Constructing my_plugin_sum" << std::endl;
    }

    std::string name() const {
        return "sum";
    }

    float calculate(float x, float y) {
        return x + y;
    }

    ~my_plugin_sum() {
        std::cout << "Destructing my_plugin_sum ;o)" << std::endl;
    }
};

// Exporting `my_namespace::plugin` variable with alias name `plugin`
// (Has the same effect as `BOOST_DLL_ALIAS(my_namespace::plugin, plugin)`)
extern "C" BOOST_SYMBOL_EXPORT my_plugin_sum plugin;
my_plugin_sum plugin;

} // namespace my_namespace

Usage: note that append_decorations is the way for seeking platform specific naming convention.

Eg: libplugin.so on linux or plugin.dll on windows.

#include <boost/dll/import.hpp> // for import_alias
#include <iostream>
#include "../tutorial_common/my_plugin_api.hpp"

namespace dll = boost::dll;

int main(int argc, char* argv[]) {

    boost::dll::fs::path lib_path(argv[1]);             // argv[1] contains path to directory with our plugin library
    boost::shared_ptr<my_plugin_api> plugin;            // variable to hold a pointer to plugin variable
    std::cout << "Loading the plugin" << std::endl;

    plugin = dll::import<my_plugin_api>(          // type of imported symbol is located between `<` and `>`
        lib_path / "my_plugin_sum",                     // path to the library and library name
        "plugin",                                       // name of the symbol to import
        dll::load_mode::append_decorations              // makes `libmy_plugin_sum.so` or `my_plugin_sum.dll` from `my_plugin_sum`
    );

    std::cout << "plugin->calculate(1.5, 1.5) call:  " << plugin->calculate(1.5, 1.5) << std::endl;
}


To create an object from plugin, here's the factory example. First, make a factory method returns boost::shared_ptr<my_plugin_aggregator>.

#include <boost/dll/alias.hpp> // for BOOST_DLL_ALIAS   
#include "../tutorial_common/my_plugin_api.hpp"

namespace my_namespace {

class my_plugin_aggregator : public my_plugin_api {
    float aggr_;
    my_plugin_aggregator() : aggr_(0) {}

public:
    std::string name() const {
        return "aggregator";
    }

    float calculate(float x, float y) {
        aggr_ += x + y;
        return aggr_;
    }

    // Factory method
    static boost::shared_ptr<my_plugin_aggregator> create() {
        return boost::shared_ptr<my_plugin_aggregator>(
            new my_plugin_aggregator()
        );
    }
};


BOOST_DLL_ALIAS(
    my_namespace::my_plugin_aggregator::create, // <-- this function is exported with...
    create_plugin                               // <-- ...this alias name
)

} // namespace my_namespace

Load creator method and create object.

#include <boost/dll/import.hpp> // for import_alias
#include <boost/function.hpp>
#include <iostream>
#include "../tutorial_common/my_plugin_api.hpp"

namespace dll = boost::dll;

int main(int argc, char* argv[]) {

    boost::dll::fs::path shared_library_path(argv[1]);                  // argv[1] contains path to directory with our plugin library
    shared_library_path /= "my_plugin_aggregator";
    typedef boost::shared_ptr<my_plugin_api> (pluginapi_create_t)();
    boost::function<pluginapi_create_t> creator;

    creator = boost::dll::import_alias<pluginapi_create_t>(             // type of imported symbol must be explicitly specified
        shared_library_path,                                            // path to library
        "create_plugin",                                                // symbol to import
        dll::load_mode::append_decorations                              // do append extensions and prefixes
    );

    boost::shared_ptr<my_plugin_api> plugin = creator();
    std::cout << "plugin->calculate(1.5, 1.5) call:  " << plugin->calculate(1.5, 1.5) << std::endl;
    std::cout << "plugin->calculate(1.5, 1.5) second call:  " << plugin->calculate(1.5, 1.5) << std::endl;
    std::cout << "Plugin Name:  " << plugin->name() << std::endl;
}

Note: When creator is destroyed, dynamic library is unloaded as well. Dereferencing plugin after library unloaded is undefined behavior.

Louis Go
  • 2,031
  • 2
  • 15
  • 26
  • What about a mechanism for running initialization code? – einpoklum Aug 07 '20 at 14:44
  • @einpoklum Thanks for the suggestion. It makes the answer more complete. Though I wish there is another answer for loading cross platform dynamic library other than boost. – Louis Go Aug 08 '20 at 02:29
  • Suggest renaming `my_plugin_aggregator` into something like `adder` or `adder_plugin`. The current name indicates that the aggregator aggregates plugins. – einpoklum Aug 08 '20 at 10:19
  • Also, I still don't see you running initialization code... – einpoklum Aug 08 '20 at 10:22
  • @einpoklum Example code is copied from document ation of boost so I keep it as is. Could you elaborate on "a mechanism for running initialization code"? I thought you meant how to create an object from dynamic library but it seems I understood it in a wrong way. – Louis Go Aug 08 '20 at 11:45