A simple example in Linux: Runtime dynamic linking and classes

Started by
0 comments, last by Bregma 16 years, 9 months ago
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.
Advertisement
Yes, using the factory function pattern is a classic way of handling dynamic link libraries. Of course, it has nothing to do with Linux or Unix and will work exatly the same way on MS Windows with the LoadLibrary() call.

You really need to put the create_bar() factory function in youy header.hpp file, since it's a part of the interafce of Foo. And you might consider changing its name to create_foo(), since it returns a pointer to a Foo object.

In an ELF system such as Linux (and in a MACH-O system such as OS X) the vtbl of a C++ object with virtual functions is normally an exported symbol. Because the compiler takes care of name mangling, all you really need to do is make sure your header file matches your shared library or bundle (a simple version check function with external "C" linkage is useful here) and that the compiler vendors and versions match between your program and your dynamically loaded object, and the rest is just like magic. Just get a pointer to the class defined in the header file and invoke its virtual functions.

I'm sure COFF systems like Windows have the same feature.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement