Untitled :: Boost.OpenMethod

This section discusses how OpenMethod interoperates with shared libraries on Linux, other POSIX-like platforms, and Windows.

## Static Linking

Statically linking to a shared library behaves the same way as linking to a static library on all platforms. Objects from the program and the shared libraries can contribute classes, methods and overriders to the default registry, or any registries.

## Dynamic Linking

By "dynamic linking", we mean a program loading a shared library after it has started, and accessing its content. A common application of dynamic linking is to implement plugin architectures.

OpenMethod uses global data to keep track of methods, overriders and classes, all managed by static constructors and destructors. `initialize` uses that information to set up dispatch tables. These variables must be truly global and unique for the library to operate correctly.

Under this condition, a shared library can dynamically add classes, methods and overriders to an existing registry. `initialize` must be called to rebuild the dispatch tables after loading or unloading a shared library.

Dynamic linking on Linux and POSIX-like platforms fulfills the unicity requirement with little effort from the programmer. On Linux, it is just a matter of tossing in `-rdynamic`, or adding `ENABLE_EXPORTS ON` to the library’s target properties if using `cmake`.

If a library only uses its own registries, for example, if using open-methods as an implementation detail, it has its own global data, and there is no need to call `initialize`.

Let’s look at an example. The following header is included by the program and the shared library:

    // animals.hpp

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

    struct Animal { virtual ~Animal() {} };
    struct Herbivore : Animal {};
    struct Carnivore : Animal {};

    BOOST_OPENMETHOD(
        meet, (
            boost::openmethod::virtual_ptr<Animal>,
            boost::openmethod::virtual_ptr<Animal>),
        std::string);

The shared library contains an object that adds two overriders, a new class, `Tiger`, and a factory function:

    // extensions.cpp
    #include "animals.hpp"

    using namespace boost::openmethod::aliases;

    BOOST_OPENMETHOD_OVERRIDE(
        meet, (virtual_ptr<Herbivore>, virtual_ptr<Carnivore>), std::string) {
        return "run";
    }

    BOOST_OPENMETHOD_OVERRIDE(
        meet, (virtual_ptr<Carnivore>, virtual_ptr<Herbivore>), std::string) {
        return "hunt";
    }

    struct Tiger : Carnivore {};

    BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore);

    extern "C" {
    #ifdef _WIN32
    __declspec(dllexport)
    #endif
    auto make_tiger() -> Animal* {
        return new Tiger;
    }
    }

The main program adds a couple of classes then calls `meet` method. At this point, we only have the catch-call overrider:

    // dynamic_main.cpp

    #include "animals.hpp"

    #include <boost/openmethod.hpp>
    #include <boost/openmethod/initialize.hpp>
    #include <boost/dll/shared_library.hpp>
    #include <boost/dll/runtime_symbol_info.hpp>
    #include <iostream>
    #include <memory>

    using namespace boost::openmethod::aliases;

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

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

    BOOST_OPENMETHOD_OVERRIDE(
        meet, (virtual_ptr<Animal>, virtual_ptr<Animal>), std::string) {
        return "greet";
    }

    int main() {
        std::cout << "Before loading the shared library.\n";

        boost::openmethod::initialize();

        std::cout << "cow meets wolf -> "
                  << meet(*std::make_unique<Cow>(), *std::make_unique<Wolf>())
                  << "\n"; // greet
        std::cout << "wolf meets cow -> "
                  << meet(*std::make_unique<Wolf>(), *std::make_unique<Cow>())
                  << "\n"; // greet

        // to be continued...

        return 0;
    }

We load the shared library using Boost.DLL. After calling `initialize`, the new overriders are installed. The main program can also use `Tiger` objects, even though it has no knowledge of that class at compile time.

    int main() {
        // ...

        std::cout << "\nAfter loading the shared library.\n";

        boost::dll::shared_library lib(
            boost::dll::program_location().parent_path() / LIBRARY_NAME,
            boost::dll::load_mode::rtld_now);
        boost::openmethod::initialize();

        std::cout << "cow meets wolf -> "
                  << meet(*std::make_unique<Cow>(), *std::make_unique<Wolf>())
                  << "\n"; // run
        std::cout << "wolf meets cow -> "
                  << meet(*std::make_unique<Wolf>(), *std::make_unique<Cow>())
                  << "\n"; // hunt

        auto make_tiger = lib.get<Animal*()>("make_tiger");
        std::cout << "cow meets tiger -> "
                  << meet(
                         *std::make_unique<Cow>(),
                         *std::unique_ptr<Animal>(make_tiger()))
                  << "\n"; // hunt

        return 0;
    }

Finally, we unload the shared library and call `initialize` again. The overriders provided by the shared library are removed from the method.

        // ...

        std::cout << "\nAfter unloading the shared library.\n";

        lib.unload();
        boost::openmethod::initialize();

        std::cout << "cow meets wolf -> "
                  << meet(*std::make_unique<Cow>(), *std::make_unique<Wolf>())
                  << "\n"; // greet
        std::cout << "wolf meets cow -> "
                  << meet(*std::make_unique<Wolf>(), *std::make_unique<Cow>())
                  << "\n"; // greet

        return 0;
    }

## Windows

If we try the example on Windows, the result is disappointing:

    Before loading the shared library.
    cow meets wolf -> greet
    wolf meets cow -> greet

    After loading the shared library.
    cow meets wolf -> greet
    wolf meets cow -> greet
    cow meets tiger -> unknown class struct Tiger

What happens here is that the program and the DLL have their own copies of "global" variables. When the DLL is loaded, its static constructors run, and they add overriders to their copy of the method (the `method::fn` static variable for the given name and signature). They are ignored when the main program calls `initialize`.

Likewise, `BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore)` in the DLL adds `Tiger` to the DLL’s copy of the registry. For the perspective of the program’s registry, the class does not exist.

In theory, this can be fixed by adding `declspec(dllimport)` and `declspec(dllexport)` attributes where needed. However, this is not practical, because programs and DLLs can both import and export registries and methods. The underlying objects are instantiated from templates, which complicates the matter. Research is being done on this subject. However, as of now, dynamic loading is supported on Windows only if it does not attempt to share a registry across modules.

## Indirect Vptrs

`initialize` rebuilds the v-tables in the registry. This invalidates all the `virtual_ptr`s, and also the v-table pointers stored in objects by `inplace_vptr_base`, related to that registry. This is seldom an issue, as most programs that dynamically load shared libraries do so at the very beginning of their execution.

Otherwise, indirect v-table pointers must be used. This is achieved by using a registry that contains the `indirect_vptr` policy. `<boost/openmethod/default_registry.hpp>` provides an `indirect_registry` that has the same policies as `default_registry`, plus `indirect_vptr`. We can use it to override the default registry, for example using a compiler command-line switch (`-DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry`).

Here is an example of a program that carries `virtual_ptr`s across `initialize` calls:

    // indirect_main.cpp

    #include "animals.hpp"

    #include <boost/openmethod.hpp>
    #include <boost/openmethod/initialize.hpp>
    #include <boost/openmethod/interop/std_unique_ptr.hpp>
    #include <boost/dll/shared_library.hpp>
    #include <boost/dll/runtime_symbol_info.hpp>
    #include <iostream>

    using namespace boost::openmethod::aliases;

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

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

    BOOST_OPENMETHOD_OVERRIDE(
        meet, (virtual_ptr<Animal>, virtual_ptr<Animal>), std::string) {
        return "greet";
    }

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

        std::cout << "Before loading the shared library.\n";
        boost::openmethod::initialize();

        auto gracie = make_unique_virtual<Cow>();
        auto willy = make_unique_virtual<Wolf>();

        std::cout << "cow meets wolf -> " << meet(*gracie, *willy) << "\n"; // greet
        std::cout << "wolf meets cow -> " << meet(*willy, *gracie) << "\n"; // greet
        std::cout << "cow.vptr() = " << gracie.vptr() << "\n"; // 0x5d3121d22be8
        std::cout << "wolf.vptr() = " << willy.vptr() << "\n"; // 0x5d3121d22bd8

        std::cout << "\nAfter loading the shared library.\n";

        boost::dll::shared_library lib(
            boost::dll::program_location().parent_path() / LIBRARY_NAME,
            boost::dll::load_mode::rtld_now);

        boost::openmethod::initialize();

        std::cout << "cow meets wolf -> " << meet(*gracie, *willy) << "\n"; // run
        std::cout << "wolf meets cow -> " << meet(*willy, *gracie) << "\n"; // hunt
        std::cout << "cow.vptr() = " << gracie.vptr() << "\n"; // 0x5d3121d21998
        std::cout << "wolf.vptr() = " << willy.vptr() << "\n"; // 0x5d3121d21988

        return 0;
    }

This program loads a shared library that is itself compiled with `-DBOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry`.

Edit this Page