I was fiddling about with using `libdl' to link to dynamic libraries at runtime, and how best to expose classes. The trouble with member functions is that they have unpredictable symbol names and can't be given extern "C" linkage. And if there are hundreds of functions in hundreds of classes, it'll be a real hassle to load them all. And the member functions would have to be wrappers around static function pointers. And all sorts of other uglinesses would probably rear their head. So you really don't want to do it that way.
On a hunch, I checked to see if I could declare a class with virtual functions in the main executable and have them dispatched to an implementation in a library. Then I'd only have to import a factory function from the library and use that to grab a pointer to an appropriate instance. Handily, it worked perfectly.
Here's the code I used. This is just a simple proof of concept.
// header.hpp
// Contains the superclass definition. This is shared between executable and library.
#ifndef header_hpp
#define header_hpp
class Foo
{
public:
virtual ~Foo () {}
virtual void bar () = 0;
};
#endif
// export.cpp
// Contains the subclass and the factory.
#include <iostream>
#include "header.hpp"
// Nothing special is needed for this. Just write the class however you normally would.
class Bar: public Foo
{
public:
virtual ~Bar ()
{
std::cout << "Going away from Bar.\n";
}
virtual void bar ()
{
std::cout << "Bar\n";
}
};
// The factory function needs extern "C" linkage or else you'll have to guess its decorated
// name when importing it, and nobody wants to do that.
extern "C" {
Foo * create_bar ()
{
return new Bar();
}
}
// import.cpp
// This is the main executable. It loads the library, invokes the factory and calls the
// virtual functions.
#include <iostream>
#include <dlfcn.h>
#include "header.hpp"
Foo * (*create_bar) ();
int main (int argc, char *argv[])
{
// Quick and ugly hack. In reality, you'd most probably want to wrap up 'dl' in
// a dynamic loading class.
void *p;
p = dlopen("./libexport.so", RTLD_NOW);
if (!p) {
std::cout << "Couldn't open library\n";
return 1;
}
create_bar = reinterpret_cast<Foo * (*)()>(dlsym(p, "create_bar"));
if (!create_bar) {
std::cout << "Couldn't find `create_bar'\n";
return 1;
}
// This just returns a normal pointer-to-Foo.
Foo * f = create_bar();
// Now you can use it like any other Foo.
f->bar();
delete f;
}
# SConstruct
# The scons script for building the thing.
SharedLibrary("export", "export.cpp")
Program("import", "import.cpp", LIBS="dl")
# I don't use Makefiles, but if you don't and won't use scons, the following sequence of
# commands will build everything:
# g++ -fPIC -c -o export.os export.cpp
# g++ -c -o import.o import.cpp
# g++ -o import import.o -ldl
# g++ -shared -o libexport.so export.os
This simple example should extend to any kind of module where you'd want to be able to load it at runtime and where the slight overhead of a virtual function call is acceptable. e.g. Video or sound drivers, physics simulator module (software vs. physx, say), weapons and monsters (as a possibly-faster-depending-upon-circumstances but still extensible-without-recompiling-the-whole-program alternative to scripts), scripting engines (e.g. you build in Lua support; somebody else could write an engine for Python support).
This is only tested on a Linux-based system, but should probably work on any system with dlopen and friends. FreeBSD and recent versions of OS X, for example. Windows has a different set of functions for loading dynamic libraries, but the basic concept ought to apply.