Introduction

No app is ever complete and you’ll never get to the point where users stop asking for new features. Sometimes those feature are low priorities so you’ll never get to them or they’re inane and you’ll never touch the idea with a 10 foot pole.

This happened to me when I was working on an ebook editor a number of years ago. There were so many features people wanted that I just didn’t have time for or didn’t like. This always caused contention and ended up being a drain on resources.

We can make a plugin system where third parties can write their must wanted additions without having to get deep into the app’s code. It also means those features that don’t fit with your visions don’t need to be added to the core app.

Previously I wrote how to make a plugin system in Lua. While Lua is a great language and Lua makes writing plugins easy, it’s not always the right choice. This time lets’ make a plugin system that allows us to write our plugins in C. C plugins can be used as thin wrappers around plugins written in other languages. It makes a great base framework for even further extensibility.

The Plugins

The first thing we need is an API that plugins will implement. There will be different types of plugins and they’ll identify themselves to the app knows what they can do and so it can call the right plugin provided functions.

Next we need a second API of function that will be exposed by the app to the plugins. This way plugins can be two way the app can call it and plugins can call into the app.

Plugin API

The API will be broken into a series of headers so we can keep everything straight. These headers need to be public and installed with the app so plugins made outside of the apps source code can build.

plugin.h

#ifndef __PLUGIN_H__
#define __PLUGIN_H__

#include <stdbool.h>
#include "app_funcs.h"

typedef enum {
    PLUGIN_TYPE_UNKNOWN = 0,
    PLUGIN_TYPE_TXT,
    PLUGIN_TYPE_INFO,
    PLUGIN_TYPE_NUM
} plugin_type_t;

bool plugin_init(void);
void plugin_deinit(void);

plugin_type_t plugin_type(void);
const char *plugin_name(void);
int plugin_version(void);
int plugin_api_version(void);

#endif /* __PLUGIN_H__ */

This is the base header which defines basic info about a plugin. Most of the functions here are required. We also list the various types of plugins the app supports.

The init and deinit functions are optional and will allow plugins to initialize themselves in some way.

The name, type, version and api_version functions are all required. Without these we won’t be able to reference, or know how to use, a given plugin.

Let’s say we have two plugins with the same name. This, obviously, is going to cause a conflict. In this situation we’ll assume the user is loading two different versions of the same plugin. To deal with this we look at what version is already loaded and keep the newest.

The api_version function is particularly important because over time our API will evolve. A version of the app that implements api_version 2 won’t be able to use a plugin that is written for api_version 3.

We should expand this later to include more metadata about a plugin. For example, author, date, description. But that’s not something we need right now.

Plugin type headers

We currently have three types of plugins as defined in the main plugin header. Each one of the these headers defines each types plugin specific functions. Right now we have a plugin that gives info about some text. One that does some math. Finally, one that does transformations on text. Let’s take a look at some simple headers that define these types of plugins.

plugin_info.h

#ifndef __PLUGIN_INFO_H__
#define __PLUGIN_INFO_H__

char *txt_info(const char *txt);

#endif /* __PLUGIN_INFO_H__ */

plugin_txt.h

#ifndef __PLUGIN_TXT_H__
#define __PLUGIN_TXT_H__

char *txt_transform(const char *txt);

#endif /* __PLUGIN_TXT_H__ */

plugin_num.h

#ifndef __PLUGIN_NUM_H__
#define __PLUGIN_NUM_H__

void num_math(int a);

#endif /* __PLUGIN_NUM_H__ */

App functions available to plugins

We said we’d have some functions exposed by the app so a plugin can have two way interaction with the app. So, let’s provide some.

app_funcs.h

#ifndef __APP_FUNCS_H__
#define __APP_FUNCS_H__

int the_num_get(void);
void the_num_set(int a);

#endif /* __APP_FUNCS_H__ */

If we wanted to provide access to objects used by the app, we would need a public header with a typedef for the object and some accessors. It’s a bad idea to put an object’s struct implementation in the header because it would make it much harder to change later. We could break plugins if we reordered or removed things from the struct.

Plugin Manager

Since we have all these plugins we need a way for the app to manage them. We’ll use a plugin manager which handles loading, unloading, initialization, deinitialization, and what not.

plugin_manager.h

#ifndef __PLUGIN_MANAGER_H__
#define __PLUGIN_MANAGER_H__

#include <list_str.h>
#include "plugin.h"

struct pm_manager;
typedef struct pm_manager pm_manager_t;

pm_manager_t *pm_load(void);
void pm_unload(pm_manager_t *pm);

int pm_plugin_version(pm_manager_t *pm, const char *name);
plugin_type_t pm_plugin_type(pm_manager_t *pm, const char *name);

list_str_t *pm_plugins_txt_transform(const pm_manager_t *pm);
list_str_t *pm_plugins_txt_info(const pm_manager_t *pm);
list_str_t *pm_plugins_num(const pm_manager_t *pm);

char *pm_plugin_txt_tranform(pm_manager_t *pm, const char *name, const char *txt);
char *pm_plugin_txt_info(pm_manager_t *pm, const char *name, const char *txt);
void pm_plugin_num_math(pm_manager_t *pm, const char *name, int a);

#endif /* __PLUGIN_MANAGER_H__ */

Plugins will be referenced by name and we’ll have accessors for each plugin type functions. By using name references we don’t have to expose plugin types to the app itself. Instead the manger object is passed to a function for a specific type with the name of that type plugin to use. We also have functions to list the name of loaded plugins for a given type. So we actually know what can be called.

Implementation

So, how do we actually load a plugin? We use dlopen. This is Unix specific but Windows does have a very similar LoadLibrary function. We’re not going to deal with this right now but the process is similar and it would be trivial to write a plugin manager that can work on Unix and Windows.

We do need a few data types that C doesn’t natively provide to make our lives easier. Specially, we’ll want a string list and a generic hashtable. These will make it much easier for us to track and reference plugins.

plugin_manager.c

Includes

#include <dlfcn.h>
#include <dirent.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <htable.h>
#include <list_str.h>

#include "plugin_manager.h"

We have a slew of includes because we need them.

Function types

typedef bool (*cpm_init)(void);
typedef void (*cpm_deinit)(void);
typedef const char *(*cpm_name)(void);
typedef int (*cpm_version)(void);
typedef int (*cpm_api_version)(void);
typedef char *(*cpm_txt_transform)(const char *in);
typedef char *(*cpm_txt_info)(const char *in);
typedef void (*cpm_num_math)(int a);
typedef plugin_type_t (*cpm_type)(void);  

Remember all of those functions we have in the plugin headers? We need to make them typedefs here so we can use them as function pointers. For every plugin we’ll create a plugin object which we can use to call into each plugin respectively.

Internal objects

typedef struct {
    void          *handle;
    cpm_init       init;
    cpm_deinit     deinit;
    cpm_name       name;
    cpm_version    version;
    plugin_type_t  type;
} pgen_t;

typedef struct {
   pgen_t           *gen; 
   cpm_txt_transform transform;
} ptxt_transform_t;

typedef struct {
   pgen_t      *gen; 
   cpm_txt_info info;
} ptxt_info_t;

typedef struct {
   pgen_t      *gen; 
   cpm_num_math num;
} pnum_math_t;

All plugins share a specific set of base functions which are put into a base plugin object. This is then embedded into a plugin specific object. I chose to go with a different plugin object for each type because I find it easer to manage. Each internal function takes the specific plugin object so we get some nice compiler warnings if we make a mistake. This does mean we have a lot of redundant code that is just different enough we can’t combine into plugin agnostic functions, but type safety is worth the extra code. It’s not that much code and it’s not that hard to manage.

Alternatively we could have used one plugin object with a union inside to group plugin specific functions. That would mean less functions since they would use the type to determine what needs to happen. This is the way to go if we weren’t using a plugin manager and instead were exposing the plugins directly to the app.

struct pm_manager {
    htable_t *ptxt_transform;
    htable_t *ptxt_info;
    htable_t *pnum_math;
};

The plugin manager object ties all the plugins together. We’re using a series of hashtables which use the plugin name as the key and the plugin object as the value. By keeping the plugin types separate we can have different types of plugins use the same name. This is not a feature, by the way, that should be relied on.

Using a name based accessing scheme is really handy for us. This is precisely how we keep the plugins abstracted to the manager. A good way to work with this scheme is to put the plugins into a menu (in a GUI app) and let users run plugins at their leisure.

Versioning

static int api_version = 2;

Since this took me two rewrites I’m saying this is API two. Remember any plugin that implement a newer API won’t load. However, we should maintain backwards compatibility so plugins that implement an older API will still work. The app will add new plugin features and we don’t want to force plugin authors to rebuild and re-release in sync with the app. To this end, ABI compatibility be maintained as long as possible.

Right now we’re using a simple incrementing versioning scheme. I’ve found this to be sufficient and unnecessary to use a three octet scheme like you’d use with the app itself. Plugins are designed to be decoupled from the app so the plugin versioning doesn’t need to exactly track or mimic the app’s version or versioning.

Plugin specific cleanup

static void ptxt_transform_destroy(ptxt_transform_t *p)
{
    free(p->gen);
    if (p->gen->deinit != NULL)
        p->gen->deinit();
    free(p);
}

static void ptxt_info_destroy(ptxt_info_t *p)
{
    free(p->gen);
    if (p->gen->deinit != NULL)
        p->gen->deinit();
    free(p);
}

static void pnum_math_destroy(pnum_math_t *p)
{
    free(p->gen);
    if (p->gen->deinit != NULL)
        p->gen->deinit();
    free(p);
}

Since we have all these plugin object’s we need functions to clean them up. This is the cleanup for each plugin type itself. There is more cleanup that will happen in the main cleanup function. That function calls these ones.

Hash table helpers

static unsigned int fnv1a_hash_str(const void *in, unsigned int seed)
{
    unsigned int  h = seed; /* default offset_basis: 2166136261 */
    unsigned int  c;
    size_t        len;
    size_t        i;

    len = strlen((const char *)in);
    for (i=0; i<len; i++) {
        c = ((const unsigned char *)in)[i];
        h ^= c;
        h *= 16777619; /* FNV prime */
    }

    return h;
}

static bool htable_str_eq(const void *a, const void *b)
{
    return (strcmp(a, b)==0)?true:false;
}

Obviously since we’re using a hashtable we need the hashtable specific functions that make it work.

Loading plugins

static bool pm_load_txt_transform(pm_manager_t *pm, pgen_t *gen)
{
    ptxt_transform_t *p;
    ptxt_transform_t *p2;
    p = calloc(1, sizeof(p));
    p->gen = gen;
    p->transform = dlsym(p->gen->handle, "txt_transform");
    if (p->transform == NULL) {
        ptxt_transform_destroy(p);
        return false;
    }
    if (htable_get(pm->ptxt_transform, (void *)gen->name(), (void **)&p2) &&
            gen->version() > p2->gen->version())
    {
        ptxt_transform_destroy(p);
        return false;
    }
    htable_insert(pm->ptxt_transform, (void *)p->gen->name(), p);
    return true;
}

static bool pm_load_txt_info(pm_manager_t *pm, pgen_t *gen)
{
    ptxt_info_t *p;
    ptxt_info_t *p2;
    p = calloc(1, sizeof(p));
    p->gen = gen;
    p->info = dlsym(p->gen->handle, "txt_info");
    if (p->info == NULL) {
        ptxt_info_destroy(p);
        return false;
    }
    if (htable_get(pm->ptxt_info, (void *)gen->name(), (void **)&p2) &&
            gen->version() > p2->gen->version())
    {
        ptxt_info_destroy(p);
        return false;
    }
    htable_insert(pm->ptxt_info, (void *)p->gen->name(), p);
    return true;
}

static bool pm_load_num_math(pm_manager_t *pm, pgen_t *gen)
{
    pnum_math_t *p;
    pnum_math_t *p2;
    p = calloc(1, sizeof(p));
    p->gen = gen;
    p->num = dlsym(p->gen->handle, "num_math");
    if (p->num == NULL) {
        pnum_math_destroy(p);
        return false;
    }
    if (htable_get(pm->pnum_math, (void *)gen->name(), (void **)&p2) &&
            gen->version() > p2->gen->version())
    {
        pnum_math_destroy(p);
        return false;
    }
    htable_insert(pm->pnum_math, (void *)p->gen->name(), p);
    return true;
}

These are all really similar and this is what I mean about redundant code. Each one of these creates a plugin object and references the plugin specific functions. dlsym is the magic that reaches into a plugin and pulls out the functions we want. Since we’re using this as a function pointer, we don’t have to worry about symbolic conflicts. I’m sure you noticed that to make plugins work every plugin needs to implement the same functions, which in a normal situation will cause all sorts of problems. But since we’re using dlopen and dlsym we don’t need to worry about this.

static void pm_load_gen(pm_manager_t *pm, void *handle)
{
    pgen_t          *gen;
    const char      *const_temp;
    cpm_type         typef;
    cpm_api_version  api_ver;
    bool             ret;

    gen = calloc(1, sizeof(*gen));

    api_ver = dlsym(handle, "plugin_api_version");
    if (api_ver == NULL || api_ver() > api_version) {
        free(gen);
        return;
    }

    typef = dlsym(handle, "plugin_type");
    if (typef == NULL) {
        free(gen);
        return;
    }
    gen->type = typef();

    gen->name = dlsym(handle, "plugin_name");
    if (gen->name == NULL) {
        free(gen);
        return;
    }
    const_temp = gen->name();
    if (const_temp == NULL || *const_temp == '\0') {
        free(gen);
        return;
    }

    gen->init = dlsym(handle, "init");
    if (gen->init != NULL) {
        if (!gen->init()) {
            free(gen);
            return;
        }
    }

    gen->handle = handle;
    gen->deinit = dlsym(handle, "deinit");
    gen->version = dlsym(handle, "plugin_version");

    switch (gen->type) {
        case PLUGIN_TYPE_TXT:
            ret = pm_load_txt_transform(pm, gen);
            break;
        case PLUGIN_TYPE_INFO:
            ret = pm_load_txt_info(pm, gen);
            break;
        case PLUGIN_TYPE_NUM:
            ret = pm_load_num_math(pm, gen);
            break;
        case PLUGIN_TYPE_UNKNOWN:
            ret = false;
            break;
    }

    if (!ret)
        free(gen);
}

When we load plugins we need to check the general functions all plugins need to implement. This lets us know if what we’re trying to load is even one of our plugins. It also does all the checks we need like the api_version.

static void pm_load_plugs(pm_manager_t *pm)
{
    DIR           *dp;
    struct dirent *ep;
    void          *handle;
    char           path[256];

    dp = opendir("./plugins");
    if (dp == NULL)
        return;

    ep = readdir(dp);
    while (ep != NULL) {
        if (ep->d_type != DT_REG) {
            ep = readdir(dp);
            continue;
        }
        
        snprintf(path, sizeof(path), "./plugins/%s", ep->d_name);
        handle = dlopen(path, RTLD_NOW);
        dlerror();
        if (handle != NULL)
            pm_load_gen(pm, handle);
        ep = readdir(dp);
    }
}

pm_manager_t *pm_load(void)
{
    pm_manager_t *pm;
    htable_cbs    cbs = {
        (htable_kcopy)strdup,
        (htable_kfree)free,
        NULL,
        NULL, 
    };

    pm = calloc(1, sizeof(*pm));
    cbs.val_free       = (htable_vfree)ptxt_transform_destroy;
    pm->ptxt_transform = htable_create(fnv1a_hash_str, htable_str_eq, &cbs);

    cbs.val_free  = (htable_vfree)ptxt_info_destroy;
    pm->ptxt_info = htable_create(fnv1a_hash_str, htable_str_eq, &cbs);

    cbs.val_free  = (htable_vfree)pnum_math_destroy;
    pm->pnum_math = htable_create(fnv1a_hash_str, htable_str_eq, &cbs);

    pm_load_plugs(pm);
    return pm;
}

To actually load the plugins we need to create the plugin manager object and iterate over all files in the plugin directory. This is hard coded to be in the same dir as the app. We’ll want to make this a compile time and or run time path because Unix doesn’t really like having app specific extras in the same dir as the app. Not to mention it’s going to be very difficult for users to install third party plugins into the installation directory, which is probably managed by their system package manager. For now though, this is convenient for development.

Unloading plugins

void pm_unload(pm_manager_t *pm)
{
    if (pm == NULL)
        return;
    htable_destroy(pm->ptxt_transform);
    htable_destroy(pm->ptxt_info);
    htable_destroy(pm->pnum_math);
    free(pm);
}

Eventually we’ll need to shutdown our app and unload all our plugins. We want to explicitly unload the plugins because there is an optional deinit function which a plugin could use to record some state to disk. So don’t rely on the app closing and all memory being freed. Unload the plugins!

Destroying the hashtables will call the registered destory callback function. Which just so happens to be our plugin specific cleanup handler.

Listing loaded plugins

static list_str_t *pm_plugins(htable_t *ht)
{
    list_str_t    *ls;
    htable_enum_t *he;
    const char    *name;

    ls = list_str_create(LIST_STR_NONE);
    he = htable_enum_create(ht);
    while (htable_enum_next(he, (void **)&name, NULL)) {
        list_str_append(ls, name);
    }
    htable_enum_destroy(he);

    return ls;
}

list_str_t *pm_plugins_txt_transform(const pm_manager_t *pm)
{
    if (pm == NULL)
        return NULL;
    return pm_plugins(pm->ptxt_transform);
}

list_str_t *pm_plugins_txt_info(const pm_manager_t *pm)
{
    if (pm == NULL)
        return NULL;
    return pm_plugins(pm->ptxt_info);
}

list_str_t *pm_plugins_num(const pm_manager_t *pm)
{
    if (pm == NULL)
        return NULL;
    return pm_plugins(pm->pnum_math);
}

Since we have all these plugins we need to reference each one that’s been loaded by name. So we provide the app some accessors to get this info.

Plugin specific functions

char *pm_plugin_txt_tranform(pm_manager_t *pm, const char *name, const char *txt)
{
    ptxt_transform_t *p; 

    if (pm == NULL)
        return NULL;
    p = htable_get_direct(pm->ptxt_transform, (void *)name);
    if (p == NULL)
        return NULL;
    
    if (p->transform == NULL)
        return NULL;
    return p->transform(txt);
}

char *pm_plugin_txt_info(pm_manager_t *pm, const char *name, const char *txt)
{
    ptxt_info_t *p; 

    if (pm == NULL)
        return NULL;
    p = htable_get_direct(pm->ptxt_info, (void *)name);
    if (p == NULL)
        return NULL;
    
    if (p->info == NULL)
        return NULL;
    return p->info(txt);
}

void pm_plugin_num_math(pm_manager_t *pm, const char *name, int a)
{
    pnum_math_t *p;

    if (pm == NULL)
        return;
    p = htable_get_direct(pm->pnum_math, (void *)name);
    if (p == NULL)
        return;
    
    if (p->num == NULL)
        return;
    p->num(a);
}

Last but not least, the manager needs to let us use the plugins. These correspond to the functions in the plugin specific headers. The plugin manager uses the name we pass in and runs the appropriate function.

Some plugins

Now we need a few example plugins. There are all pretty much self explanatory.

cplug_info_cnt.c

#include <stdio.h>
#include <string.h>
#include "plugin.h"
#include "plugin_info.h"

plugin_type_t plugin_type(void)
{
    return PLUGIN_TYPE_INFO;
}

const char *plugin_name(void)
{
    return "a_sp_cnt";
}

int plugin_version(void)
{
    return 3;
}

int plugin_api_version(void)
{
    return 2;
}

char *txt_info(const char *in)
{
    char   out[256];
    size_t len;
    size_t i;
    size_t ac = 0;
    size_t sp = 0;

    len = strlen(in);
    for (i=0; i<len; i++) {
        if (in[i] == 'a') {
            ac++;
        } else if (in[i] == ' ') {
            sp++;
        }
    }

    snprintf(out, sizeof(out), "There %s %zu 'a' character%s and %s %zu space character%s",
            ac==1?"is":"are", ac, ac==1?"":"s",
            sp==1?"is":"are", sp, sp==1?"":"s");
    return strdup(out);
}

cplug_num_a.c

#include <stdbool.h>
#include "plugin.h"
#include "plugin_num.h"

static int mynum;

plugin_type_t plugin_type(void)
{
    return PLUGIN_TYPE_NUM;
}

bool init(void)
{
    mynum = 4;
    return true;
}

const char *plugin_name(void)
{
    return "a_sp_cnt";
}

int plugin_version(void)
{
    return 100;
}

int plugin_api_version(void)
{
    return 2;
}

void num_math(int a)
{
    int new_num;

    new_num = the_num_get();
    new_num += (a * mynum);
    the_num_set(new_num);

    mynum++;
}

Text plugins

To make our text plugins a bit easier to work with, we’re going to use the handy string builder we made awhile back.

cplug_txt_lower.c

#include <ctype.h>
#include <string.h>
#include <str_builder.h>
#include "plugin.h"
#include "plugin_txt.h"

plugin_type_t plugin_type(void)
{
    return PLUGIN_TYPE_TXT;
}

const char *plugin_name(void)
{
    return "to_lower";
}

int plugin_version(void)
{
    return 2;
}

int plugin_api_version(void)
{
    return 1;
}

char *txt_transform(const char *in)
{
    str_builder_t *sb;
    char          *out;
    size_t         len;
    size_t         i;

    if (in == NULL)
        return NULL;

    sb = str_builder_create();
    len = strlen(in);
    for (i=0; i<len; i++) {
        str_builder_add_char(sb, tolower(in[i]));
    }

    out = str_builder_dump(sb, NULL);
    str_builder_destroy(sb);
    return out;
}

cplug_txt_upper.c

#include <ctype.h>
#include <string.h>
#include <str_builder.h>
#include "plugin.h"
#include "plugin_txt.h"

plugin_type_t plugin_type(void)
{
    return PLUGIN_TYPE_TXT;
}

const char *plugin_name(void)
{
    return "to_upper";
}

int plugin_version(void)
{
    return 1;
}

int plugin_api_version(void)
{
    return 1;
}

char *txt_transform(const char *in)
{
    str_builder_t *sb;
    char          *out;
    size_t         len;
    size_t         i;

    if (in == NULL)
        return NULL;

    sb = str_builder_create();
    len = strlen(in);
    for (i=0; i<len; i++) {
        str_builder_add_char(sb, toupper(in[i]));
    }

    out = str_builder_dump(sb, NULL);
    str_builder_destroy(sb);
    return out;
}

cplug_txt_upper2.c

#include <string.h>
#include "plugin.h"
#include "plugin_txt.h"

plugin_type_t plugin_type(void)
{
    return PLUGIN_TYPE_TXT;
}

const char *plugin_name(void)
{
    return "to_upper2";
}

int plugin_version(void)
{
    return 1;
}

int plugin_api_version(void)
{
    return 17;
}

char *txt_transform(const char *in)
{
    (void)in;
    return strdup("NOPE");
}

The cplug_txt_upper2.c doesn’t do anything and is just there to test out the whole API version thing.

The test app

Instead of writing a full app like a text editor let’s write a little test which will exercise the plugin manager.

Implementation

main.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <list.h>
#include "plugin_manager.h"

static pm_manager_t *pm        = NULL;;
static int           the_num   = 7;
static int           other_num = 19;

int the_num_get(void)
{
    return the_num;
}

void the_num_set(int a)
{
    the_num = a;
}

int main(int argc, char **argv)
{
    const char *txt = "This is a test of the GREATEST plugins";
    const char *name;
    list_str_t *plugs;
    char       *out;
    size_t      len;
    size_t      i;

    pm = pm_load();

    printf("Manipulation text: %s\n", txt);
    printf("\n");

    plugs = pm_plugins_txt_transform(pm);
    len = list_str_len(plugs);
    printf("TXT Transform Plugins (%zu):\n", len);
    for (i=0; i<len; i++) {
        name = list_str_get(plugs, i);
        printf("PLUGIN: %s\n", name);
        out = pm_plugin_txt_tranform(pm, name, txt);
        printf("%s\n", out);
        free(out);
        printf("\n");
    }
    list_str_destroy(plugs);

    plugs = pm_plugins_txt_info(pm);
    len = list_str_len(plugs);
    printf("TXT Info Plugins (%zu):\n", len);
    for (i=0; i<len; i++) {
        name = list_str_get(plugs, i);
        printf("PLUGIN: %s\n", name);
        out = pm_plugin_txt_info(pm, name, txt);
        printf("%s\n", out);
        free(out);
        printf("\n");
    }
    list_str_destroy(plugs);

    plugs = pm_plugins_num(pm);
    len = list_str_len(plugs);
    printf("Nums Plugins (%zu):\n", len);
    printf("the_num: %d\n", the_num);
    for (i=0; i<len; i++) {
        name = list_str_get(plugs, i);
        printf("PLUGIN: %s\n", name);
        pm_plugin_num_math(pm, name, other_num);
        other_num += 4;
        printf("the_num: %d\n", the_num);
        printf("\n");
    }
    list_str_destroy(plugs);

    pm_unload(pm);
    return 0;
}

This app is going to create a plugin manager and it will load our plugins. We’ll run though each plugin for each type and run it.

Building

CMakeLists.txt

cmake_minimum_required (VERSION 3.0)

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins)

include_directories (
    ${CMAKE_CURRENT_BINARY_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/include

    # Location of helper object headers.
    ../../str
    ../../ds
)

# Build our app.
set (APP_SOURCES
    plugin_manager.c

    # We have the helper objects here instead of making them into libraries.
    ../../ds/list.c
    ../../ds/list_str.c
    ../../ds/htable.c
    ../../str/str_builder.c
    main.c
)
add_executable(app ${APP_SOURCES})
set_target_properties(app PROPERTIES ENABLE_EXPORTS ON)


# Build all of our plugins.
set(UPPER_SOURCES
    plugins/cplug_txt_upper.c
    ../../str/str_builder.c
)
add_library(upper MODULE ${UPPER_SOURCES})
target_link_libraries(upper app)


set(UPPER2_SOURCES
    plugins/cplug_txt_upper2.c
)
add_library(upper2 MODULE ${UPPER2_SOURCES})
target_link_libraries(upper2 app)


set(LOWER_SOURCES
    plugins/cplug_txt_lower.c
    ../../str/str_builder.c
)
add_library(lower MODULE ${LOWER_SOURCES})
target_link_libraries(lower app)


set(INFO_SOURCES
    plugins/cplug_info_cnt.c
)
add_library(info MODULE ${INFO_SOURCES})
target_link_libraries(info app)


set(NUM_SOURCES
    plugins/cplug_num_a.c
)
add_library(num MODULE ${NUM_SOURCES})
target_link_libraries(num app)

One thing to keep in mind, this links the plugins to the app itself. We want to do this for testing because we want to know if we’re using a function the app doesn’t provide. This way we can catch typos or API version mismatches. For a release we should use flags that ignore undefined functions at link time and let everything be resolved at run time. This allows people writing plugins do compile without having the app installed. Unless we’re using Windows where we don’t have a choice and have to link to the app.

$ mkdir build && cd build
$ cmake ..
$ make -j9

Running

$ ./app 
Manipluation text: This is a test of the GREATEST plugins

TXT Transform Plugins (2):
PLUGIN: to_upper
THIS IS A TEST OF THE GREATEST PLUGINS

PLUGIN: to_lower
this is a test of the greatest plugins

TXT Info Plugins (1):
PLUGIN: a_sp_cnt
There is 1 'a' character and are 7 space characters

Nums Plugins (1):
the_num: 7
PLUGIN: a_sp_cnt
the_num: 83

Every plugin except to_upper2 was loaded and ran. Also, each did the right thing. Looks to me like we can use plugins in our apps.

Other Thoughts

We’ve looked at loading plugins by name and manually calling them. Another way we could handle plugins, is by having the app provide hooks a plugin can register itself to. For example, in a text editor, there could be a save hook. Any plugin which has registered a save hook function would be called on save, so it can make any changes to the text in process. A great plugin for a text editor save hook is trimming white space at the end of every line.