Adding Plugin Capabilities to Your Code

by Tom Bradley

Editors' Note: The following article appears in the November issue of our sister on-line publication, Linux Gazette.

The days of a program existing 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 you can have a more enriched surfing experience. Music players, such as XMMS, use plugins to support different encodings and visual plugins so you can watch music dance on the screen. This article shows how to provide plugin support to your programs. I use module and plugin interchangeably; for purposes of this article they are the same.

How to Work with Plugins

Only four functions are needed to work with plugins. They are part of the dl (Dynamic Loader) library. Below is a brief introduction to them here. You can view the info pages for each of them to get a more in-depth description.

  • dlopen: used to load a module into memory

  • dlclose: used to unload the module from memory

  • dlsym: used to look up and return the address of a function inside a module

  • dlerror: returns an error message

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.

#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 and 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. I can use this 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

Two Simple Plugins

Now that we have a loader we need some plugins for it to load. There is no defined prototype for a module's 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

   
int entry()
{
printf("I am module one!\n");
return 0;
}

module2.c

int entry()
{
printf("I am module two!\n");
 return 0;
}

To compile the plugins, enter:

$ 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 plugins are compiled, the first being th -fPIC option. PIC stands for position independent code; this tells the compiler the code should be set up to use a relative address space. In other words, the code can be placed anywhere in memory and the loader redefines the addresses at load time. The -shared option tells the compiler the code should be compiled in a way that allows it to be linked by another executable. That is, the .so (shared object) will act in a fashion similar to the library; however, your .so is not a library and cannot be linked using -l with gcc.

Using the Loader

Here are the commands for using the two different plugins and their output:

                $ ./loader module1.so
                I am module one!
                $ ./loader module2.so
                I am module two!
Adding Bookkeeping Functions for Plugins

This section assumes you are using GCC, and the commands used are specific to GCC. Other compilers may have similar features; check you documentation for compatibility. GCC provides an __attribute__ option that can be used with functions. This option offers many useful features to functions. I discuss only two of them here: constructor and destructor. See the info page on GCC for descriptions of the other attributes.

The ELF (executable and linkable format) binary provides two sections, .init and .fini, that can contain code to be 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 allows you to initialize variables or do other bookkeeping responsibilities your module may require. For example, you could have the module read the variables from the main program it will need to get started, or you could 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 the plugin in question provides. In my example it provided only one function, entry. Your plugin 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 should be placed for easier reading. You should avoid several function names because GCC uses these names, including _init, _fini, _start and _end. To see a full listing of functions and variables that GCC creates, 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. The destructor attribute tells the compiler the location of the corresponding function in the .fini section.

Conclusion

With the use of the dl library, it is a simple task to provide plugin support to your program, allowing for easy expandability and flexibility. Although this example demonstrates grabbing only one function from a plugin, it is easy to grab multiple functions and use them as if they were part of the original program.

Load Disqus comments

Firstwave Cloud