Ikke's Blog

Post details: GModules and vtables

May 19
GModules and vtables

After the article I posted yesterday on GModules, I just wrote some sample code using a vtable to look up functions, which makes module handling much easier.

I guess we better go straight to the code:

First we need to define some structure that represents our vtable. I did this in a common header file, vtable-common.h:

#ifndef _VTABLE_COMMON_H
#define _VTABLE_COMMON_H

#include <glib.h>

typedef void (* voidvoidfunc) (void);
typedef gint (* intvoidfunc) (void);
typedef void (* voidstringfunc) (gchar *s);
typedef gboolean (* boolpstringfunc) (gchar **ps);
/* This function will demonstrate NULL function pointers */
typedef void (* foofunc) (void);

typedef struct VtableTest {
        voidvoidfunc funcone;
        intvoidfunc functwo;
        voidstringfunc functhree;
        boolpstringfunc funcfour;
        foofunc funcfive;
} VtableTest;

/* Module init function */
typedef VtableTest * (* VtableTestInit) (void);

#endif

First we define typedefs for all function prototypes we want in our vtable, then we define the prototype of a module init function.

This may look a bit strange, let's take a look at the module code (in vtable-module.c) to see what we can do with this:

#include <glib.h>
#include <gmodule.h>

#include "vtable-common.h"

/* Our vtable function implementations */
static void one() {
        g_print("[M] Function 1\n");
}

static gint two() {
        g_print("[M] Function 2\n");
        return 2;
}

static void three(gchar *s) {
        g_print("[M] Function 3: %s\n", s);
}

static gboolean four(gchar **s) {
        g_print("[M] Function 4\n");
        *s = g_strdup("four");
        return TRUE;
}

/* Our function table.
 * As you can see, we put functions in there as if they're normal variables */
static VtableTest table = {
        one,
        two,
        three,
        four,
        /* This module does not implement funcfive */
        NULL
};

/* This is the module init function.
 * It's a "VtableTestInit" function, as defined in vtable-common.h */
G_MODULE_EXPORT VtableTest * vtable_module_init() {
        g_debug("[M] Initializing module");
        /* Of course you can do a lot in this function,
         * we don't need to do anything in this sample */
        /* Return a reference to the function table */
        return &table;
}

The comments in the code should explain every step pretty well.
As you can see we only export one function, vtable_module_init. This function got a fixed name (so we should define this name in our API). It returns a pointer to a vtable of type VtableTest, which includes pointers to all function implementations.

Last but not least is the main code, in vtable-main.c:

#include <glib.h>
#include <gmodule.h>

#include "vtable-common.h"

gint main(gint argc, gchar *argv[]) {
        /* Same handles as yesterday */
        GModule *module = NULL;
        VtableTest *moduletable = NULL;
        gchar *modulepath = NULL, *dir = NULL;
        VtableTestInit moduleinitfunc = NULL;
        /* Function value */
        gint two = 0;
        gboolean fourb = FALSE;
        gchar *fours = NULL;
        
        /* Same stuff as yesterday */
        if(g_module_supported() == FALSE)
                g_error("No module support");

        dir = g_get_current_dir();
        modulepath = g_module_build_path((const gchar *) dir, "vtabletestmodule");
        g_debug("Module path: %s", modulepath);
        module = g_module_open(modulepath, G_MODULE_BIND_LAZY);
        g_free(dir);
        g_free(modulepath);
        if(module == NULL)
                g_error("Unable to load module");

        /* We need to lookup one function, which inits our vtable */
        if(g_module_symbol(module, "vtable_module_init", (gpointer *) &moduleinitfunc) == FALSE) {
                g_error("Unable to get reference to the module init function: %s", g_module_error());
        }

        /* Get a reference to the module function table */
        moduletable = moduleinitfunc();

        /* Run all our functions, providing parameters or fetching return
         * values where necessary */
        if(moduletable->funcone != NULL) {
                g_debug("Running module funcone");
                moduletable->funcone();
        }
        else {
                g_warning("Funcone is NULL");
        }

        if(moduletable->functwo != NULL) {
                g_debug("Running module functwo");
                two = moduletable->functwo();
                g_debug("functwo returned %d", two);
        }
        else {
                g_warning("Functwo is NULL");
        }

        if(moduletable->functhree != NULL) {
                g_debug("Running module functhree");
                moduletable->functhree("vtable-module-test");
        }
        else {
                g_warning("Functhree is NULL");
        }

        if(moduletable->funcfour != NULL) {
                g_debug("Running module funcfour");
                fourb = moduletable->funcfour(&fours);
                g_debug("funcfour returned \"%s\", string value is \"%s\"", fourb == TRUE? "true" : "false", fours);
        }
        else {
                g_warning("Funcfour is NULL");
        }

        if(moduletable->funcfive != NULL) {
                g_debug("Running module funcfive");
                moduletable->funcfive();
        }
        else {
                g_warning("Funcfive is NULL");
        }

        /* As yesterday, close the module */
        if(g_module_close(module) == FALSE) {
                g_error("Unable to close module: %s", g_module_error());
        }

        return 0;
}

These are the steps we take:

  • Load the module (see the previous article for more information on this)
  • Look up one symbol, "vtable_init_module". As mentioned before, this is the fixed name symbol that should be exported from our module.
  • run "vtable_module_init", so we get a reference to the module's vtable
  • Now we can use all functions refered to in the vtable. Make sure you always check for NULL pointers, or your application will crash. Even if your API/documentation states a module author must implement all vtable functions, checks don't hurt :-) Notice we call the functions using moduletable->foofunc(), so the vtable members are really normal function pointers, nothing fancy here.
  • We clean up by closing the module

You can compile everything with this simple Makefile (yes I know it's a bad one):

default: main libvtabletestmodule.so
all: default

main: vtable-main.c vtable-common.h
	gcc -o main -g `pkg-config --cflags --libs glib-2.0 gmodule-2.0` vtable-main.c

libvtabletestmodule.so: vtable-module.c vtable-common.h
	gcc -o libvtabletestmodule.so -g -shared `pkg-config --cflags --libs glib-2.0 gmodule-2.0` vtable-module.c

or execute the commands by hand, of course.

Here's the output:

** (process:16338): DEBUG: Module path: /home/foo/bar/vtable/libvtabletestmodule.so
** (process:16338): DEBUG: [M] Initializing module
** (process:16338): DEBUG: Running module funcone
[M] Function 1
** (process:16338): DEBUG: Running module functwo
[M] Function 2
** (process:16338): DEBUG: functwo returned 2
** (process:16338): DEBUG: Running module functhree
[M] Function 3: vtable-module-test
** (process:16338): DEBUG: Running module funcfour
[M] Function 4
** (process:16338): DEBUG: funcfour returned "true", string value is "four"

** (process:16338): WARNING **: Funcfive is NULL

As you can see, all this is quite logical and easy to write once you figure out how to. The provided functionality can be very useful though.
Notice thanks to glib, the code presented here should compile and run under Linux, Solaris and all other supported platforms (yes, even on Windows using DLL's) without any code change.

A little exercise for the reader: currently we got the module name "vtabletestmodule" hardcoded in vtable-main.c. What to do if we have several VtableTest implementations, e.g. vtabletestmodule1 and vtabletestmodule2? We could loop through all files in ".", find out whether they're a valid module (ie try to load them), if they're a valid module, try to figure out whether it exports "vtable_module_init", if that's the case, get a reference to the module's vtable, and use it. This is eg the way Gaim loads all it's plugins (although the module files aren't stored in "." of course). It's not too difficult to implement this, but the result is quite impressive too, so give it a try :-)

Comments:

No Comments for this post yet...

Leave a comment:

Your email address will not be displayed on this site.
Your URL will be displayed.

Allowed XHTML tags: <p, ul, ol, li, dl, dt, dd, address, blockquote, ins, del, span, bdo, br, em, strong, dfn, code, samp, kdb, var, cite, abbr, acronym, q, sub, sup, tt, i, b, big, small>
(Line breaks become <br />)
(Set cookies for name, email and url)
(Allow users to contact you through a message form (your email will NOT be displayed.))

Categories

Who's Online?

  • Guest Users: 456

Misc

XML Feeds

What is RSS?