Main program

Overview

The main program is the part of executable that is located outside the plug-in framework. The main program is responsible for setting up the plug-in framework and for loading the desired set of plug-ins. The main program should preferably be very thin, a mere plug-in loader, because it can not fully participate in plug-in interaction. C-Pluff distribution provides a plug-in loader, cpluff-loader, which can be used as a generic main program for arbitrary plug-in collections.

Responsibilities

The main program has several responsibilities:

Initializing the plug-in framework

Plug-in framework, or the C-Pluff library, must be initialized before its services can be used. Initialization is not a thread-safe operation and should generally be done by the main program before any additional plug-in framework accessing threads are started. Initialization is done by calling cp_init. Additionally, the main program can use cp_set_fatal_error_handler to register a function that is called when a fatal error occurs. A fatal error is one that prevents the framework from continuing operation. For example, errors in operating system locking operations and a NULL pointer being passed as an argument which is expected to have a non-NULL value are fatal erors.

Here is an example of possible initialization code.

 #include <locale.h>
 #include <cpluff.h>

 void handle_fatal_error(const char *msg) {

   // ... log error, flush logs, send bug report, etc. ...

   fprintf(stderr, "A fatal error occurred: %s\n", msg);
   abort();
 }

 void initialize(void) {
   cp_status_t status;

   setlocale(LC_ALL, "");
   cp_set_fatal_error_handler(handle_fatal_error);
   status = cp_init();
   if (status != CP_OK) {
     // ... handle initialization failure ...
   }
 }

Creating a plug-in context

A plug-in context represents the co-operation environment of a set of plug-ins from the perspective of a particular participating plug-in or the perspective of the main program. From main program perspective, a plug-in context is a container for a set of plug-ins. A plug-in can interact with other plug-ins in the same container.

An extensible application can have more than one plug-in container but usually one container should suffice. Due to the nature of C programs, plug-ins deployed to different containers are not very well insulated from each other. For example, global variables provided by a plug-in in one container are visible to all plug-ins in all containers. Also, by placing all plug-ins in the same container they can more efficiently share common base components which themselves might provide extensibility.

A main program creates a plug-in context, to be used as a container for plugins, using cp_create_context.

 #include <cpluff.h>

 cp_context_t *ctx;

 void create_context(void) {
   cp_status_t status;

   ctx = cp_create_context(&status);
   if (ctx == NULL) {
     // ... handle initialization failure ...
   }
 }

Loading plug-ins

An extensible application is made of plug-ins that can be added and removed dynamically. The plug-ins are loaded by the main program using the services provided by the framework. The framework provides couple of alternative ways of loading plug-ins.

As a lowest level operation, the main program can load individual plug-ins from known locations using cp_load_plugin_descriptor and cp_install_plugin. Here is example code that loads a set of plug-ins from file system locations listed in a file.

 #include <stdio.h>
 #include <cpluff.h>

 extern cp_context_t *ctx;
 static const char pluginListFile[] = "/etc/example/plugins.list";

 void load_plugins(void) {
   char plugindir[128];
   FILE *lf;

   // Open plug-in list file
   lf = fopen(pluginListFile, "r");
   if (lf == NULL) {
     // ... handle loading failure ...
   }

   // Load each listed plug-in
   while (fgets(plugindir, 128, lf) != NULL) {
     cp_plugin_info_t *plugininfo;
     cp_status_t status;
     int i;

     // Remove possible trailing newline from plug-in location
     for (i = 0; plugindir[i + 1] != '\0'; i++);
     if (plugindir[i] == '\n') {
       plugindir[i] = '\0';
     }

     // Load plug-in descriptor
     plugininfo = cp_load_plugin_descriptor(ctx, plugindir, &status);
     if (pinfo == NULL) {
       // ... handle loading failure ...
     }

     // Install plug-in descriptor
     status = cp_install_plugin(ctx, plugininfo);
     if (status != CP_OK) {
       // ... handle loading failure ...
     }

     // Release plug-in descriptor information
     cp_release_info(ctx, plugininfo);
   }

   // Close plug-in list file
   fclose(lf);
 }

Alternatively, the main program can register and load plug-in collections. A plug-in collection is a file system directory which includes individual plug-ins in subdirectories, one plug-in in each subdirectory. Plug-in collections can be registered with a plug-in context using cp_register_pcollection. Plug-ins of the collection can then be scanned and loaded using cp_scan_plugins. Here is example code loading plug-ins from a plug-in collection.

 #include <cpluff.h>

 extern cp_context_t *ctx;
 static const char pluginCollectionDir[] = "/etc/example/plugins";

 void load_plugins(void) {
   cp_status_t status;

   status = cp_register_pcollection(ctx, pluginCollectionDir);
   if (status != CP_OK) {
     // ... handle loading failure ...
   }
   status = cp_scan_plugins(ctx, 0);
   if (status != CP_OK) {
     // ... handle loading failure ...
     // (notice that some plug-ins might have been loaded)
   }
 }

Controlling plug-in execution

The main program controls plug-in execution by starting and stopping plug-ins and by executing run functions registered by plug-ins. Additionally, the main program can pass startup arguments to plug-ins.

When plug-ins are installed they are not yet activated and their runtime library is not even loaded at that point. The main program typically activates plug-ins by starting a main plug-in responsible for user interface or core application logic. This plug-in then implicitly causes other plug-ins to be activated via dependencies and by dynamically resolving symbols provided by other plug-ins. Plug-ins recursively activate each other until all initially needed plug-ins have been started. Some plug-ins might be activated at a later time when their functionality is needed, for example due to user action.

If a plug-in needs to perform background operations, that is operations executed outside the invocation of plug-in provided interface functions, then it can either start a new thread or it can register a run function. A run function is a function that is typically executed as part of the main loop by the main program.

The following example code shows how a main program might initialize plug-in startup arguments using cp_set_context_args, start the core plug-in using cp_start_plugin and then execute plug-in run functions using cp_run_plugins.

 #include <cpluff.h>

 extern cp_context_t *ctx;
 static const char corePluginId[] = "org.example.core";

 void run_plugins(char *argv[]) {
   cp_status_t status;

   // Set plug-in startup arguments
   cp_set_context_args(ctx, argv);

   // Start the core plug-in, possibly activating other plug-ins as well
   status = cp_start_plugin(ctx, corePluginId);
   if (status != CP_OK) {
     // ... handle startup failure ...
   }

   // Execute plug-ins until there is no more work to be done
   cp_run_plugins(ctx);
 }

 int main(int argc, char *argv[]) {
   // ... do initialization and load plug-ins ...

   run_plugins(argv);

   // ... do destruction ...
 }

Alternatively, if the main program has some operations it must perform as part of the main loop, the call to cp_run_plugins can be replaced by code using cp_run_plugins_step like in the following example.

 void mainloop(void) {
   int finished = 0;

   while (!finished) {
     // ... do main program specific operations ...

     finished = !cp_run_plugins_step(ctx);
   }
 }

Changing plug-in configuration

C-Pluff has been designed to allow dynamic changes to the plug-in configuration, that is plug-ins being added or removed without shutting down the application or the framework. It is the responsibility of the main program to manage such changes if the application is to support dynamic configuration changes.

Adding plug-ins is straightforward because there is no need to consider dependencies of active plug-ins. For example, if one uses plug-in collections as introduced above then new plug-ins can be deployed under the plug-in collection directory while the application is running and the main program can load them incrementally by calling cp_scan_plugins again. This call might be activated by some user interface element, for example a plug-in manager component which just downloaded and installed new plug-ins as requested by the user. The flags CP_SP_STOP_ALL_ON_INSTALL and CP_SP_RESTART_ACTIVE orred together can be used to cause all active plug-ins to be restarted if they do not otherwise notice the extensions provided by new plug-ins.

Upgrading plug-ins is almost as straightforward because the C-Pluff framework manages plug-in dependencies (assuming the plug-ins have declared their dependencies properly). The new version of a plug-in can be deployed under the plug-in collection directory in a new subdirectory parallel to the old version while the application is running. The main program can then call cp_scan_plugins with CP_SP_UPGRADE and CP_SP_RESTART_ACTIVE orred together. This will stop the old version of the upgraded plug-in (implicitly stopping all plug-ins that depend on it), unload the plug-in from the framework, install the new version of the plug-in and finally restart plug-ins that were active before the operation. The old version of the plug-in can now be removed from the plug-in collection. Again, CP_SP_STOP_ALL_ON_UPGRADE can be added to restart all active plug-ins.

Deleting plug-ins must be done by first stopping and unloading the plug-in to be deleted using cp_uninstall_plugin. The the plug-in can be removed from the plug-in collection.

Destroying the plug-in framework

The plug-in framework can be destroyed and all resources released by calling cp_destroy as many times as cp_init has been called. This is not a thread-safe operation and should generally be done by the main program just before application exits. The destroy function stops and unloads all plug-ins and destroys all plug-in contexts before destroying the core framework.

Individual plug-in contexts can be destroyed by calling cp_destroy_context. The destroy function stops and unloads all plug-ins before destroying the context itself.


Generated on Fri Apr 6 15:40:55 2007 for C-Pluff C API by doxygen 1.5.1