Using dynamically loadable objects under Linux
brioche/aspirine

00 - Table of Contents

	     01 - Introduction
	02 - The Dynamic Linker and its Programming Interface
	03 - A Naive Approach
	04 - Designing a More Flexible Plug-in System
	05 - Closing Words and Acknowledgements

01 - Introduction

Hello folks!

I'm really happy today because I've decided to write an article about my personal two favorite topics: C++ and Unix programming. Well, I also hope that you'll find a piece of interrest in this too...

Today we will discuss Unix shared libraries. Most of the discussed material of course works with C too but I think it's tremendously funnier to do it with objects. :)

All the code provided with this article is compiled with egcs-2.91.60 running under GNU/Linux 2.0.36, glibc2. Although the fact the API should be pretty portable the conventions might change on some systems (symbol naming convention with or without the leading underscore for instance), just check out the manpage and you'll quickly know how to fix it.

02 - The Dynamic Linker and its Programming Interface

(If you already know what a dynamic library is, skip the next paragraph.)

A dynamic library is a set of functions (with their associated static data) which will be loaded in memory only at run time. Dynamic libraries involves smaller executable size and memory footprint since it allows you to load part of you code only when required or to create plug-ins for your programs. This is on that last topic that we'll have in our focus today.

The Dynamic Linker (DL) is the utility used to resolve symbols at runtime. The DL imports functions in the address space of the calling program.

The Dynamic Linker Programming Interface basically provides three C functions to retrieve the address of the functions.

The dlopen() function opens a given "shared object" (*.so) and allows you to specify a mode.

- RTLD_NOW forces the DL to check for all unresolved dependencies
- RTLD_LAZY will check for unresolved symbols when code is ran

It's better to do all the checking during the opening of the shared object to avoid any useless overhead during the execution.

If your library doesn't reside in one of the standard place for shared libraries, you will have to indicate the full absolute pathname to the object otherwise dlopen() will fail! You may chage the LD_LIBRARY_PATH environment variable either but it might be dangerous...

The function dlsym() returns the address of the function whose symbol has been specified. For instance:

	     typedef double (*trig_func)(double);

	// Open the math dynamic library
	void* handle = dlopen("libm.so");
	trig_func cosine = (trig_func)dlsym("cos");
	cout << "cos(0.5) = " << cosine(0.5) << endl;
	dlclose(handle);

I'm sure you already guessed what dlclose() does... :)

There's one big thing you've to know is than when dlclose() has been called, you won't be able to access any dynamically loaded symbols related to the handle closed! Keep that in mind...

You can also have an _init() routine to be automatically called before dlopen() returns. This might be useful if you have to initialized some internal data structures.

When exporting function compiled with a C++ compiler, don't forget the extern "C" {} statement otherwise you'll have a rough time to find the right symbol to resolve since C++ adds a signature to all functions.

Some systems might need extra compilation parameters so please, don't flame For more details about this API, consult the manpage.

03 - A Naive Approach

What we really want to do is to be able to dynamicaly load C++ objects whose code is stored in a shared library.

Importing an object from a shared library is somewhat harder than importing a simple C function. An object is obviously not a function, it's a set of functions. So what we could do is to link the code of all the function in a constructor parametrized with the name of the plugin. It would give something like this:

  class Plugin {

    // Pointers to the dynamically loaded methods
    void (*_f)();
    int (*_g)(float, const char*);

  public:

    Plugin(Dynamic_linker* dl);

    // Plugin methods
    void f() { _f(); }
    int g(float f, const char* s) { return _g(); }

  };

  // ...

  // This is the C/C++ we love! :)
  Plugin::Plugin(Dynamic_linker* dl)
  {
    _f = (void (*_f)())dl->resolve("f");
    _g = (int (*_g)(float, const char*))dl->resolve("g");
  }

This solution works but is totally unapropriate because it's a real pain in the ass to write and maintain! You have to write a function for all the methods of the plugin and the implementation of the plugin can't even access the instance attributes! We would have to pass a structure to all functions... Ok, let's forget it, we didn't learn C++ to get back to those good old C hacks! :) It's time for some brainstorming...

04 - Designing a More Flexible Plug-in System

First, let's define the interface to be implemented by all Plug-ins.

	     class Plugin {
	public:
	  // Interface
	  virtual const char* info() const = 0;
		  virtual void greet(const char* who) = 0;
	};

The info() method may be used to gather information when listing all the loaded plugins and thus to show to other coders that you are a huge elite scener because your demo runs with Plug-ins. :)

The greet() method will just print some fancy message on the standard output but we could of course imagine making a lot of other nice things and I'm sure you already have some ideas of your own...

Now, our main objective will be to minimize the number and the complexity of the routines to be written to transform any class into a Plug-in.

Q - But what's the thing we want to have at last?
A - An instance of a given plugin.

So why exporting the code for the whole Plugin when we only need to export the code a function which creates an instance of the right Plug-in?

That was too easy! For each concrete Plug-in, we'll just provide a C function (a factory) which creates an "object" and then export that function. In the example the function is called "create()" and returns a void pointer. The rationale is simple, it uses a C calling convention so:

	     // Get a reference to the Plugin factory stored in
	// the shared library opened in dl
	Plugin::Factory factory = (Plugin::Factory)dl.resolve("create");

	// Returns the implementation of the Plugin interface
	Plugin* plugin = factory();
	cout << plugin->info() << endl;

This is damn cool! Only one function to write and what a function:

	     extern "C" void* create()
	{
	  Concrete_plugin* instance = new Concrete_plugin;
	  return (void*)instance;
	}

You just have one line to change from one Plug-in to another! Unfortunately we can't use a template with C linkage, we would have saved even more lines of code!

To avoid symbol clashes, it's usually good practise to "protect" the factory method by giving it a name like "__create_plugin" as in the source code.

It would be pretty nice to be able to select the code to be loaded when invoking its constructor, wouldn't be? We can achieve that by applying the Proxy pattern (see Hugi 18). We will create a special type of concrete plugin which will encapsulate a reference to a dynamically loaded plugin. Now we can write:

	     Plugin_proxy p("plugin1.so");
	p.greet("optic/project-jazz");

Imho, it will be hard to get a Plug-in frontend which is easier to use!

The proxy implementation has one major drawback, it involves two virtual function calls instead of one. The first v-call is performed when invoking the proxy method which calls another virtual function on the real object.

If speed really matters, I propose another solution which will remove the overhead involved by the proxy object but break the interface. Instead of using a Proxy, we can use an Object Adapter. The adapter pattern looks much like a Proxy except the it doesn't inherit from the Plugin interface and thus, the class musn't implement the abstract methods. The adapter will provide exactly the same services as the Proxy we've been discussed previously.

To achieve full transparency, we can overload a couple of operators:

	     inline Plugin& Plugin_adapter::operator * ()
	{
	  return *_plugin;
	}

	inline Plugin* Plugin_adapter::operator -> ()
	{
	  return _plugin;
	}

This solution is probably better than cloning the Plugin interface since it will immune the adapter from the methods defined in the Plugin class. The only drawback is a pure syntaxical issue since you will have to use the dereference operator -> even for a non dynamically allocated object. Check out the following example:

	     Plugin_adapter p("plugin1.so");
	p->greet("bp/fuzzion");

	// Or : (*p).greet("bp/fuzzion");

The Adapter-based solution is faster than the Proxy but the code written for the proxy is perfectly interchangeable with a code directly using the Plugin objects whereas the code for the adapter might lead to some syntax oddities... It's now time to make up your mind!

To clearly summarize all this stuff, here's a simplified pseudo-UML model of the framework and a sequence diagram showing step-by-step how the plugin is created and how the request are handled by the proxy. If you have any further questions, you can always drop me an email and I'll be pleased to talk it over with you!

(This is an awful low-res version, check out the original PostScript picture enclosed in the source code archive.)

Last tip for the road ahead: for applications using a lot of Plug-in, you should consider writing some "Plug-in Manager" class that does all the administrative job... Such managers (or factories) can be implemented as containers. You should take a look at this article:

	     Implementing Abstract Factory as an STL container
	by Jason Shankel
	In Dr Dobb's Journal, "Object Oriented Programming"
	December 1997, Volume 22, Issue 12, Page 28

I've also seen a similar pattern called "Pluggable Factory" or something on the website of the C++ Report magazine but I lost the URL. So move your browser to www.google.com to look for it!

05 - Closing Words and Acknowledgements

Don't cry little (boy|girl), this is not really the end of the article, you can still delight yourself with all the source code packaged with this issue!

As usual, our world-famous guarantee applies:

"This article is money-back guaranteed so if absolutely none of the topics discussed here were useful to you, you may contact our Customer Service Hot Line. Get in touch with our friendly operators, dial 1-800-ASPI-RINE now!"

I hope there are not too many typos in the article and bugs in the code, all the stuff has been put together in a couple of evenings so... :)

Huge respect to all the aspirine freaks: Colas, Al-Najjir, Gedeon, Trax (thanks for proofreading this article!), Alphatrion, Desnos, Cray and all the others... everybody needs Aspirine!

Cheerio coders!

brioche of the aspirine d-zign gangsta kru

RCS - $Id: plugin.txt,v 1.5 2000/04/17 18:09:19 alcibiade Exp $