ALINK="#FF0000"> [ Prev ][ Table of Contents ][ Front Page ][ Talkback ][ FAQ ][ Next ]
LINUX GAZETTE
...making Linux just a little more fun!
Adding Plugin Capabilities To Your Code
By Tom Bradley

0. Introduction

The days of a program living as a single entity are all but gone. Today’s programs need to be more versatile and expandable. The simplest way to provide flexibility and expandability to your program is through the use of modules otherwise known as plugins. Web browsers and music players are two good examples of programs that allow plugins. Browsers use plugins to add support to web pages such as Java, Flash and QuickTime so that you can have a more enriched surfing experience. Music players such as XMMS use plugins to support different encodings as well as have visual plugins to watch your music dance on the screen. This article shows how to provide plugin support to your programs. Note: I use module and plugin interchangeably, for purposes of this article they are the same.

1. How To Work With Plugins

There are only four functions needed to work with plugins. They are part of the dl (Dynamic Loader) library. I will give just a brief introduction to them here. You can view the info pages for each of these to get a more in-depth description.
dlopen
This function is used to load a module into memory.
dlclose
This function is used to unload the module from memory.
dlsym
This function is used to look up and return the address of a function inside a module
dlerror
This function returns an error message to you.

2. A Simple Loader Program for Plugins

Here is the code for a simple loader program that takes the plugin name as a command line argument.

main.c
text version of this listing
  #include <unistd.h>
  #include <string.h>
  #include <errno.h>
  #include <dlfcn.h>
      
  #define PATH_LENGTH 256
      
  int main(int argc, char * argv[])
  {
      char path[PATH_LENGTH], * msg = NULL;
      int (*my_entry)();
      void * module;
      
      /* build the pathname for the module */
      getcwd(path, PATH_LENGTH);
      strcat(path, "/");
      strcat(path, argv[1]);
      
      /* load the module, and resolve symbols now */
      module = dlopen(path, RTLD_NOW);
      if(!module) {
          msg = dlerror();
          if(msg != NULL) {
              dlclose(module);
              exit(1);
          }
      }
      
      /* retrieve address of entry point */
      my_entry = dlsym(module, "entry");
      msg = dlerror();
      if(msg != NULL) {
          perror(msg);
          dlclose(module);
          exit(1);
      }
      
      /* call module entry point */
      my_entry();
      
      /* close module */
      if(dlclose(module)) {
          perror("error");
          exit(1);
      }
      
      return 0;
  }                

The code is pretty simple. After the loader loads the plugin it looks inside the plugins symbol table using the dlsym command to get the address of the function `entry.’ Once I have the address of this function I can call the function, I assign it to the function pointer that I created. Then the plugin is unloaded. The function pointer line may need some explaining.
   int (*my_entry)()
is used as a pointer to a function that takes no arguments and returns an int. Which I can use to point to the function `entry’ in the plugin.
   int entry()
The following command is used to compile the loader program:

$ gcc -o loader main.c –ldl

3. Two Simple Plugins

Now that we have a loader we need some plugins for it to load. There is no defined prototype for a modules entry point; you may use whatever you like. In my examples I have the entry point return an int and take no arguments. You can set up your entry points to take whatever arguments they need and return whatever you want. It does not need to be called `entry' either. I simply use this to make it easier to understand the purpose of the function. In addition, you may have more than one entry point into a plugin. Below are two samples of a modules, each with the same entry point:
module1.c
text version of this listing
int entry()
{
    printf("I am module one!\n");
    return 0;
}

module2.c
text version of this listing
int entry()
{
    printf("I am module two!\n");
    return 0;
}

To compile the plugins:
$ gcc -fPIC -c module1.c
$ gcc -shared -o module1.so module1.o
$ gcc -fPIC -c module2.c
$ gcc -shared -o module2.so module2.o
A couple of things are worth noting about the way these are compiled. First, the `-fPIC' flag. PIC stands for "Position Independent Code", this tells the compiler that this code should be set up to use a `relative' address space. Meaning that the code can be placed anywhere in memory and the loader takes care redefining the addresses at load time. The `-shared' flag tells the compiler that this code should be compiled in a way that allows it to linked by another executable. In other words the .so (shared object) will act in a similar fashion as library does; however, your .so is not a library and cannot be linked using the `-l' with gcc.

4. Using the Loader

Here are the commands for using the two different plugins and there output:
$ ./loader module1.so
I am module one!

$ ./loader module2.so
I am module two!  

5. Adding Bookkeeping Functions for Plugins

This section assumes you are using the gcc compiler do to the fact that the commands used are specific to gcc, other compilers may have similar features, you may check you documentation for compatibility. Gcc provides an `__attribute__' flag to be used with functions. This flag offers many useful features to functions; however, I will only discuss two of them here, see the info page on gcc for other descriptions of the other attributes. The two I wish to discuss are `constructor' and `destructor'. The ELF (Executable and Linkable Format) binary  provides two sections .init and .fini which can contain code that is executed before and after a module is loaded (in a regular program these would be run before and after main() is executed.) Placing code in these sections can allow you to initialize variables or do other bookkeeping responsibilities your module may require. For example you could have the module read variables from the main program that it will need to get started or have the plugin set variables inside the main program such as the interface type of the plugin. The interface type of a plugin is the set of commands that the plugin in question provides. In my example it provided only one function 'entry'; yours may provide others. Below is a sample of using these attributes:

__attribute__ ((constructor)) void init() 
{
  /* code here is executed after dlopen() has loaded the module */
}
      
      
__attribute__ ((destructor)) void fini() 
{
  /* code here is executed just before dlclose() unloads the module */
}    

The names init() and fini() are not necessary, I use them to clarify where these functions to be placed for easier reading. There are several function names that you must avoid because gcc uses these names. Some of which are _init, _fini, _start and _end. To see a full listing of functions and variables that gcc creates you can run `nm’ on the binary file. The `constructor' and `destructor' attributes are what tell the compiler where to place the code inside the binary file. Simply put, `constructor' tells the compiler that the corresponding function goes in the .init section of the and likewise the `destructor' attribute tells the compiler the place the corresponding function in the .fini section.

6. Conclusion

With the use of the dl library it a simple task to provide plugin support to your program. Allowing for easy expandability and flexibility. Although this example only demonstrates grabbing one function from a plugin it is easy to grab multiple functions from a plugin and use them as if they were part of the original program.
Copyright © 2002, Tom Bradley. Copying license http://www.linuxgazette.net/copying.html
Published in Issue 84 of Linux Gazette, November 2002

[ Prev ][ Table of Contents ][ Front Page ][ Talkback ][ FAQ ][ Next ]