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_ptrs, 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_ptrs 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.