Building a Plug In - VirtualRaven/mathlibra GitHub Wiki

Building a Plug-In for Mathlibra

Introduction

The Mathlibra library does just include the most basic and essential mathematical functions to the end user like sin(), sqrt() etc. Instead it was designed so that additional functions can easily be added. This design choice was made to enforces good code separation but also create greater flexibility. In this article I will show you how to add your own functions to mathlibra.

A plug-in in the context of mathlibra refers to a shared library that includes a plugin_entry function and class that inherits plugin::function_plugin_base. The plug-in shared library is loaded by Mathlibra when the the host application calls enablePlugins().

So let's begin creating this library with it's corresponding entry function and class. This tutorial will use the Mathlibra Skeleton plug-in repo by bubblefrog, so I recommend cloning it before continuing.

The class

In this section we will be using the SkeletonPlugin.h and SkeletonPlugin.cpp. In SkeletonPlugin.h we see the following class declaration.

class SkeletonPlugin : public plugin::function_plugin_base {
    function func;
public:
    virtual unsigned int function_size();
    virtual plugin::function *get_funcs();
    virtual void plugin_init_event();
    virtual void plugin_destruction_event();
    virtual const char *version_minor();
    virtual const char *version_major();
    virtual const char *plugin_name();
    SkeletonPlugin();
};

This class serves as the interface for Mathlibra to describe the plug-in. function_size() shall return the number of functions that this plug-in contains. get_func() returns a pointer to a list of plugin::function objects, these objects contains your function and it's metadata. This will become clear later. plugin_init_event() and plugin_destruction_event() are called by mathlibra at plug-in creation and unload. These functions are thought to be used for cleanup and initialization.

Note: plugin_destruction_event() must also call delete(this) as all references to the instance of the class will be dropped upon exiting this function.

The version_major(),version_minor(), and plugin_name() functions are used to retrieve plug-in name and version information.

Now lets look inside SkeletonPlugin.cpp for the actual implementation of this class. In our simple example we will only export a single function named identity, so our implementation becomes quite simple.

    unsigned int SkeletonPlugin::function_size() {
        return 1;
    }

    plugin::function *SkeletonPlugin::get_funcs() {
        return &func;
    }

    void SkeletonPlugin::plugin_init_event() {

    }

    void SkeletonPlugin::plugin_destruction_event() {
        delete(this);
    }

    const char *SkeletonPlugin::version_minor() {
        return "0";
    }

    const char *SkeletonPlugin::version_major() {
        return "1";
    }

    const char *SkeletonPlugin::plugin_name() {
        return "SkeletonPlugin";
    }

    SkeletonPlugin::SkeletonPlugin(){

        func.ptr = &identity;
        func.disp_name = "Identity Function";
        func.doc="";
        func.name="identity";
        func.tag="Skeleton plugin";
    }

As you may notice, this file does not contain anything special. The only interesting part resides inside the constructor where we initialize a function object. func.ptr= &identity sets the callback to the identity function which we are yet to implement. func.name="identity" sets which name used by the end user to call the function. The other fields are just metadata. Note the delete this statement in function_destruction_event(), omitting this will cause memory leaks, and we don't want that. It might seem dangerous to delete the this pointer without ensuring that the object resides on the heap, but you will shortly see that we know that this is true.

The entry point

As mention in the introduction we are also required to implement a plug-in entry function. If we look at the end of SkeletonPlugin.cpp we are lucky enough to find precisely that! How fortunate...

#ifdef __cplusplus
extern "C"
{
#endif
plugin::function_plugin_base *PLUGIN_ENTRY() {
    return new SkeletonPlugin();
}
#ifdef __cplusplus
}
#endif

An here is our justification for the dangerous delete this statement. Now that we have all boiler plate code lets actually implement our identity function!

The function

Now the fun part! First let's take a look at PluginFunctions.h

#include "type.h"
#include "function_helper.h"
type* identity(node_base* b);

Here we can see the signature of our function. All plug-in function callbacks have the same signature. the type is the base class of all data types (or values if you read Expressions & syntax) in mathlibra. The node_base object is not important here, we must just accept it as an argument so that we can pass it to the forward function which we will see shortly.

Mathlibra comes with a powerful function called forward which generates the wrapper code needed to interface with Mathlibra. Before explaining more let's give an example from PluginFunctions.cpp

type* __identity(double d){
    base_type<double>* b = new base_type<double>(1,1);
    auto it = b->begin();
    *it=d;
    return b;
}

type* identity(node_base* b) {
    return function_helper::forward<double>(__identity,b);
}

Here we first create a function called __identity which is our actual implementation of our plugin function. But the signature of __identity does not match that of a mathlibra callback function. Thus we create a second function which does. In the identity function we uses forward to forward the call to our real implementation. As template arguments forward takes a list of arguments that our __identity function expects. In this case double. If our __identity function takes two double as arguments we would need to change our forward call to

    return function_helper::forward<double,double>(__identity,b);

TO BE CONTINUED...

⚠️ **GitHub.com Fallback** ⚠️