A Minimal Module

A Minimal Module — Dissection of a minimal Gwyddion data processing module.

A Minimal Module

A minimal Gwyddion data-processing module could look like this:

#include <libgwyddion/gwymacros.h>
#include <libgwymodule/gwymodule.h>
#include <libprocess/datafield.h>
#include <app/gwyapp.h>

#define INVERT_VALUE_RUN_MODES \
    (GWY_RUN_NONINTERACTIVE | GWY_RUN_WITH_DEFAULTS)

static gboolean    module_register            (const gchar *name);
static gboolean    my_invert_value            (GwyContainer *data,
                                               GwyRunType run);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    "value_invert",
    "Inverts data value.",
    "J. Random Hacker <hacker.jr@example.org>",
    "1.0",
    "J. Random Hacker",
    "2004",
};

GWY_MODULE_QUERY(module_info)

static gboolean
module_register(const gchar *name)
{
    static GwyProcessFuncInfo my_invert_value_func_info = {
        "my_invert_value",
        "/_Test/_Invert Value",
        (GwyProcessFunc)&my_invert_value,
        INVERT_VALUE_RUN_MODES,
        0,
    };

    gwy_process_func_register(name, &my_invert_value_func_info);

    return TRUE;
}

static gboolean
my_invert_value(GwyContainer *data, GwyRunType run)
{
    GwyDataField *dfield;

    g_return_val_if_fail(run & INVERT_VALUE_RUN_MODES, FALSE);
    dfield = GWY_DATA_FIELD(gwy_container_get_object_by_name(data, "/0/data"));
    gwy_app_undo_checkpoint(data, "/0/data", NULL);
    gwy_data_field_invert(dfield, FALSE, FALSE, TRUE);

    return TRUE;
}

Now, we should be able to compile this code (see below), make a dynamically linked library (*.so, *.dll, depending on the operating system) and put it to your Gwyddion modules directory (e. g. /lib/gwyddion/modules/process/, however depending on your actual installation and operating system). After Gwyddion starts, the module is found and menu entry Test/Invert Value will appear in the main menu. Clicking on this menu item will invert the actual data. As this is the minimal module it even does not have graphical user interface to change parameters of data processing. However, for its purpose – data inversion – it is sufficient.

Though the above example is minimal it still constis of quite a bit of code. We will analyse it piece-by-piece in the following paragraphs.

Boilerplate

First of all, of course, some header files.

#include <libgwyddion/gwymacros.h>
#include <libgwymodule/gwymodule.h>
#include <libprocess/datafield.h>
#include <app/gwyapp.h>

These four are essential, for a complex modules you may need additional headers. gwymacros.h contains some basic macros (none of them is actually used here), gwymodule.h declares functions essential for registering the module and its features, datafield.h declared basic GwyDataField functions (like value inversion we will use in the module later), and gwyapp.h declares undo/redo functions and other main application related stuff.

Run Modes

Now we declare the run modes our module supports.

#define INVERT_VALUE_RUN_MODES \
    (GWY_RUN_NONINTERACTIVE | GWY_RUN_WITH_DEFAULTS)

This is just a convenience macro, so we don't have to type them several times manually.

There are four run modes total, though GWY_RUN_MODAL and GWY_RUN_INTERACTIVE are useful only for modules with a graphical user interface. GWY_RUN_NONINTERACTIVE is the right run mode for a noninteractive module like our example. GWY_RUN_WITH_DEFAULT is currently unused in Gwyddion, but it's a good idea (and easy) to support it too.

Function Prototypes

Function prototypes of our functions.

static gboolean    module_register            (const gchar *name);
static gboolean    my_invert_value            (GwyContainer *data,
                                               GwyRunType run);

Note all functions and global variables should be declared static, the module should export no symbol except GWY_MODULE_QUERY described below.

The Module Info Structure

Here the interesting part starts. The GwyModuleInfo structure contains overall information about the module, most of it is presented in a more-or-less human-readable form Gwyddion in the module browser.

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    "value_invert",
    "Inverts data value.",
    "J. Random Hacker <hacker.jr@example.org>",
    "1.0",
    "J. Random Hacker",
    "2004",
};

The first item is always GWY_MODULE_ABI_VERSION. The second one is a pointer to module registration function, by convention called module_register, it is described in details below.

The third item ("value_invert") is the module name. It will appear as Module in the browser. A module is uniquely identified by its name, so module name have to be unique, Gwyddion refuses to load more modules of the same name. Otherwise it is a free-form string, though by convention, it is usually kept the same as file name of the module.

The fourth item is module description. It will appear as Description in the browser. This is a short text (up to a paragraph or two) informing curious humans what the module is about.

The next item is the module author(s). Under normal circumstances this should be a name of a person (or more people). Including a contact e-mail address here it's a good idea because it will appear in the browser as Authors, so people don't need to look to the module sources to find out how to contact you.

The next item is the module version, a free-form string that will appear as Version in the browser. Though it is free-form, using a versioning scheme with alorithmically comparable versions is preferable.

The last but one and last items are module copyright and date. The copyright field may be the same as authors field (except without the eventual e-mail address), it may be an organization, or even someone else than the author.

The Module Query Function

A Gwyddion module is loaded in two stages. First, it is queried, the module responds with its module info, Gwyddion checks whether it looks good (e.g., whether module ABI version matches). If it is OK, Gwyddion continues with registration of particular module features.

The query function should be always constructed using the GWY_MODULE_QUERY macro as follows (note there is no semicolon after the right parenthesis):

GWY_MODULE_QUERY(module_info)

The module_info parameter is the module info described above. If you change its name for any reason, change it here too.

Module Feature Registration

The module registration function is called in the second registration stage and is responsible for registering particular module functions. Our module registeres only a one function, my_invert_value.

Each function type has its own registration function, our function is a data processing one, so it's registered with gwy_process_func_register(). File loading and/or saving functions are registered with gwy_file_func_register(), etc. (at the time of writing this there exist four module function types).

The registration itself is carried out by filling a function info structure and calling gwy_*_func_register() with the info. Note the function info has to be persistent (static), because Gwyddion doesn't make a copy of it. It must not be freed or changed after the registration.

static gboolean
module_register(const gchar *name)
{
    static GwyProcessFuncInfo my_invert_value_func_info = {
        "my_invert_value",
        "/_Test/My _Invert Value",
        (GwyProcessFunc)&my_invert_value,
        INVERT_VALUE_RUN_MODES,
        0,
    };

    gwy_process_func_register(name, &my_invert_value_func_info);

    return TRUE;
}

The registration function should always return TRUE. Returning FALSE means the registration failed, and Gwyddion then tries to unregister all its already registered functions and unload the module. Normally there is hardly any reason why the registration could fail.

The name argument has to be passed as the first argument of all function registration calls (gwy_process_func_register() here). Now it is identical to module name specified in module info, but don't count on it.

Each function type has a different function info structure, though some fields are present in all of them. Data processing function info GwyProcessFuncInfo consits of function name (again, this has to be an unique identifier, among the functions of the same type), path where it should be placed into the Data Process menu, a pointer to the function itself (my_invert_value), and run modes it supports (we cleverly defined the INVERT_VALUE_RUN_MODES macro for them).

Executive

Now, let's do the actuall data processing:

static gboolean
my_invert_value(GwyContainer *data, GwyRunType run)
{
    GwyDataField *dfield;

    g_return_val_if_fail(run & INVERT_VALUE_RUN_MODES, FALSE);
    dfield = GWY_DATA_FIELD(gwy_container_get_object_by_name(data, "/0/data"));
    gwy_app_undo_checkpoint(data, "/0/data", NULL);
    gwy_data_field_invert(dfield, FALSE, FALSE, TRUE);

    return TRUE;
}

A few things can be seen here. First, we check the run mode. More sofisticated modules with a GUI and their own settings can do different things based on the run mode, but we just check whether it looks sane.

Next, we get a datafield from some container. The GwyDataField object is the basic data object representing two-dimensional array of values (height field). Quite a few datafield manipulating functions already exist in libprocess, we use one of them (gwy_data_field_invert()) to perform the value inversion too.

The obscure part is getting the datafield out from some container that was passed to our function. Just take it as a fact the data are called "/0/data", it is probably equally good name as anything else. There are other strange things in the container but you'd rather not want to know about them (at least not now, some more exotic container inhabitants are described in the Beyond Minimal Module section).

The gwy_app_undo_checkpoint() creates a point in the undo history we can return to later, call it just before you start modifying the data. Its second argument is what is to be saved, so "/0/data" follows us here too. The NULL just terminates the argument list.

Then we finally invert the value with gwy_data_field_invert() and return TRUE since we modified the data. If we didn't modify them, we would have returned FALSE.