2

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
  • 118,144
  • 57
  • 340
  • 684
  • 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

5

Initializing when library loaded

Edit: Revisited the question again and I found that I didn't answer the initialization part.

The best answer I found is this one which is still platform dependent. Will update if there is a better one.

For platform specific

In windows it's DllMain but be sure to read the part of Dynamic link library best practice to see what's unsafe to call in DllMain

In linux:

__attribute__ ((constructor))
__attribute__ ((destructor)) 

Load library portably

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,213
  • 2
  • 16
  • 29
  • 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