I wanted to test what happens if executable and library share different versions of a library,
i.e. different classes with the same name. Idea: make a test
function which
is called once from the executable directly, and once with the code from the library:
MWE:
base.h defines an abstract plugin class, which can generate a port object (of type base)
struct base
{
virtual void accept(struct visitor& ) {}
virtual void test() = 0;
virtual ~base() {}
};
struct visitor
{
virtual void visit(struct base& ) {}
};
struct plugin
{
virtual ~plugin() {}
virtual base& port() = 0;
virtual void test() = 0;
};
typedef plugin* (*loader_t) (void);
plugin.cpp defines a derived plugin class, which can return a derived port (mport)
#include <iostream>
#include "base.h"
struct mport : public base
{
void accept(struct visitor& ) override {}
void test() override { std::cout << "plugin:test" << std::endl; }
virtual ~mport() = default;
};
struct port_failure_plugin : public plugin
{
void test() override final { inp.test(); }
virtual ~port_failure_plugin() {}
private:
mport inp;
base& port() override { return inp; }
};
extern "C" {
const plugin* get_plugin() { return new port_failure_plugin; }
}
host.cpp defines a derived port class with the same name (mport)
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <dlfcn.h>
#include "base.h"
struct mport : public base
{
#ifdef ACCEPT_EXTERN
void accept(struct visitor& ) override;
#else
void accept(struct visitor& ) override {}
#endif
void test() override { std::cout << "host:test" << std::endl; }
};
#ifdef ACCEPT_EXTERN
void mport::accept(struct visitor& ) {}
#endif
int main(int argc, char** argv)
{
assert(argc > 1);
const char* library_name = argv[1];
loader_t loader;
void* lib = dlopen(library_name, RTLD_LAZY | RTLD_LOCAL);
assert(lib);
*(void **) (&loader) = dlsym(lib, "get_plugin");
assert(loader);
plugin* plugin = (*loader)();
base& host_ref = plugin->port();
host_ref.test(); // expected output: "host:test"
plugin->test(); // expected output: "plugin:test"
return EXIT_SUCCESS;
}
Compile e.g.:
g++ -std=c++11 -DACCEPT_EXTERN -shared -fPIC plugin.cpp -o libplugin.so
g++ -std=c++11 -DACCEPT_EXTERN -ldl -rdynamic host.cpp -o host
The complete code is on github (try make help
)
In order to let the host run test
"like the plugin does",
it calls a virtual function, which is implemented in the plugin. So I expect that test
is called
- once from the object code of the
host
executable (expectation: "host:test") - once from the object code of the
plugin
library (expectation: "plugin:test")
The reality looks different:
- In all (of the following) cases, both outputs are equal (2x"host:test" or 2x"plugin:test")
- Compile host.cpp with
-rdynamic
, and without-DACCEPT_EXTERN
the test calls output "plugin:test" - Compile host.cpp with
-rdynamic
, and with-DACCEPT_EXTERN
(see Makefile), and the test calls call "host:test" - Compile host.cpp without
-rdynamic
, and the test calls outputplugin:test
(both intern and extern)
Questions:
- Is it even possible to call both versions of
mport::test
(e.g. executable and library)? - Why does
-rdynamic
change the behavior? - Why does
-DACCEPT_EXTERN
affect the behavior?