Dynamic Loading

OpenMethod supports dynamic loading on operating systems that are capable of handling C++ templates correctly during dynamic link. A dynamic library can add classes, methods and overriders to an existing policy. initialize must then be called to rebuild the dispatch tables.

This leads to a problem: any virtual_ptr in existence before initialize is called again becomes invalid. This also applies to vptrs that are stored inside objects by inplace_vptr.

This applies only to cases where a dynamic library adds to an existing policy. Even if the dynamic library itself uses open-methods, for example as an implementation detail, but it uses its own policy, there is no issue.

The solution is to use a policy that contains the indirect_vptr facet. Instead of storing the vptr directly, it stores a reference to the vptr.

Here is an example:

// dl.hpp

#include <string>

#include <boost/openmethod.hpp>

struct Animal {
    virtual ~Animal() {
    }
};

struct Herbivore : Animal {};
struct Carnivore : Animal {};
struct Cow : Herbivore {};
struct Wolf : Carnivore {};

struct dynamic_policy : boost::openmethod::default_registry::with<
                            boost::openmethod::policies::indirect_vptr> {};

template<class Class>
using dyn_vptr = boost::openmethod::virtual_ptr<Class, dynamic_policy>;

BOOST_OPENMETHOD(
    encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string,
    dynamic_policy);
The policy must be passed to the method as well as the virtual_ptrs.

The indirect_vptr facet tells virtual_ptr to use a pointer to the vptr. Even tough the value of the vptr changes when initialize is called, the vptrs are stored in the same place (the policy’s static_vptr<Class> variables).

We can now register the classes and and provide an overrider:

// dl_main.cpp

#include <cstring>
#include <iostream>
#include <dlfcn.h>
#include <unistd.h>

#include <boost/openmethod.hpp>
#include <boost/openmethod/unique_ptr.hpp>
#include <boost/openmethod/compiler.hpp>

#include "dl.hpp"

BOOST_OPENMETHOD_CLASSES(
    Animal, Herbivore, Cow, Wolf, Carnivore, dynamic_policy);

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (dyn_vptr<Animal>, dyn_vptr<Animal>), std::string) {
    return "ignore\n";
}

At this point we only have one overrider. Animals of all species ignore one another:

auto main() -> int {
    using namespace boost::openmethod;

    initialize<dynamic_policy>();

    std::cout << "Before loading library\n";

    auto gracie = make_unique_virtual<Cow, dynamic_policy>();
    // Wolf _willy;
    // auto willy = virtual_ptr<Wolf, dynamic_policy>(_willy);
    auto willy = make_unique_virtual<Wolf, dynamic_policy>();

    std::cout << "Gracie encounters Willy -> "
              << encounter(gracie, willy); // ignore
    std::cout << "Willy encounters Gracie -> "
              << encounter(willy, gracie); // ignore

Let’s load a dynamic library containing this code:

// dl_shared.cpp

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

#include "dl.hpp"

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (dyn_vptr<Herbivore>, dyn_vptr<Carnivore>), std::string) {
    return "run\n";
}

struct Tiger : Carnivore {};

BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore, dynamic_policy);

extern "C" auto make_tiger() -> Tiger* {
    return new Tiger;
}

BOOST_OPENMETHOD_OVERRIDE(
    encounter, (dyn_vptr<Carnivore>, dyn_vptr<Herbivore>), std::string) {
    return "hunt\n";
}

Now back to main:

    char dl_path[4096];
    dl_path[readlink("/proc/self/exe", dl_path, sizeof(dl_path))] = 0;
    *strrchr(dl_path, '/') = 0;
    strcat(dl_path, "/libdl_shared.so");
    void* handle = dlopen(dl_path, RTLD_NOW);

    if (!handle) {
        std::cerr << "dlopen() failed: " << dlerror() << "\n";
        exit(1);
    }

    std::cout << "\nAfter loading library\n";

    boost::openmethod::initialize<dynamic_policy>();

    auto make_tiger =
        reinterpret_cast<Animal* (*)()>(dlsym(handle, "make_tiger"));

    if (!make_tiger) {
        std::cerr << "dlsym() failed: " << dlerror() << "\n";
        exit(1);
    }

    std::cout << "Willy encounters Gracie -> "
              << encounter(willy, gracie); // hunt

    {
        auto hobbes = std::unique_ptr<Animal>(make_tiger());
        std::cout << "Gracie encounters Hobbes -> "
                  << encounter(gracie, *hobbes); // run
    }

After unloading the library, we must call initialize again:

    dlclose(handle);

    std::cout << "\nAfter unloading library\n";

    boost::openmethod::initialize<dynamic_policy>();

    std::cout << "Gracie encounters Willy -> "
              << encounter(gracie, willy); // ignore
    std::cout << "Willy encounters Gracie -> "
              << encounter(willy, gracie); // ignore