Writing Aion Plug-Ins

This section describes how you can write plug-ins that extend the capabilities of Aion.

This section differs from Deploying Your Algorithm. Deploying Your Algorithm describes how you can call into your algorithm or model from other code you develop. Describes the opposite capability, how to allow your algorithm or model to call into external code you develop. By using the capabilities described in both sections, you have extensive capabilities to integrate your algorithm or model into a larger product.

Inesonic provides an example plug-in with Aion that includes the full source and build environments for Windows, Linux, and MacOS. Table 34 lists where you can find the example plug-in on each platform.

Table 34 Http Example Plug-In Location

Platform

Location

Windows

%AION_ROOT%\examples\http

Linux

${AION_ROOT}/examples/http

MacOS

${AION_ROOT}/Contents/Resources/examples/http

The sample code in this section has been taken from the http example plug-in.

The Structure Of A Basic Plug-In

An Aion plug-in is composed of two distinct parts:

  • The registration library - A dynamic library that is loaded by Aion each time you start Aion. The registration library primarily contains data structures the Aion uses to identify the plug-in and to to configure the Aion GUI and Aion compiler.

  • A static or dynamic implementation library that is linked against your algorithm or model and provides the additional capabilities offered by the plug-in. The implementation library is only used if one or more of the functions provided by the plug-in are used.

The registration library is generally nothing more than a collection of data structures that Aion will identify and interpret to configure the capabilities offered by the plug-in. You can use the data structures to define plug-ins with one or more implementation libraries as well as the functions provided by each implementation library. Functions can also have multiple variants with different types and numbers of parameters.

The implementation library contains the implementations of the actual functions. Functions must be named using the C++ name manging conventions common for each platform so that variants of each function can be correctly associated with the declarations provided by the registration library.

The Plug-In Directory Structure

Aion plug-ins require a specific directory structure that is platform dependent. On Windows, plug-ins must be located under %USERPROFILE%\Aion. On MacOS and Linux, plug-ins must be located under ${HOME}/Aion. Alternately, you can locate your plug-ins in a different location using the environment variable INESONIC_USER_PLUG_IN_ROOT_DIRECTORY which can be used to change where Aion looks for user plug-ins. Simply set INESONIC_USER_PLUG_IN_ROOT_DIRECTORY to the absolute path where you want your user plug-ins to be located.

Each plug-in must be in it’s own directory underneath the user plug-in top level directory discussed above.

Figure 106 shows the required plug-in directory structure for each plug-in on Windows installations.

../_images/plug_in_directory_structure_windows.png

Figure 106 Plug-In Directory Structure on Windows Operating Systems

Figure 107 shows the required plug-in directory structure for each plug-in on Linux installations. Note that the structure is very similar to the structure used on Windows except for the extensions used for the key files.

../_images/plug_in_directory_structure_linux.png

Figure 107 Plug-In Directory Structure on Linux Operating Systems

Figure 108 shows the required plug-in directory structure for each plug-in on MacOS installations.

../_images/plug_in_directory_structure_macos.png

Figure 108 Plug-In Directory Structure on MacOS Operating Systems

Note that the registration dynamic library must be named to match the name of the plug-in and must use the extension .dll on Windows, .so on Linux and .dylib on MacOS.

On Windows, the implementation libraries must use the extensions dll or lib depending on the type of implementation library. Note that the the use of static libraries is problematic on Windows due to how library dependencies must be handled.

On Linux, the implementation libraries must use the entensions .so or .a. On MacOS, the implementation libraries must use the entensions .dylib or .a.

In addition, on MacOS and Linux, the implementation libraries must include the prefix lib.

Plug-In Build Tool Requirements

To build Aion plug-ins on Windows, you will need to use Microsoft Visual Studio 2019 and the Microsoft Visual C++ 2019 runtime libraries. Do note that future versions of Aion may use different, newer, versions of these tools when creating plug-ins. On Windows, plug-ins must be built against the MD version of the Microsoft runtime.

On MacOS, you can use the standard version of Apple XCode available through the Apple Store.

On Linux, you can use the standard version of GCC provided with most Linux distributions.

The Implementation Library

You can create an implementation library much the same way you create any other shared or static library.

When creating an implementation library, note that functions registered for use with Aion must limit their use to the exact types defined in the Model namespace as documented in Deploying Your Algorithm.

When developing on Windows, be sure to export any functions required by the plug-in using __declspec(dllexport).

If your implementation library depends on other dynamic libraries, those libraries must be located such that they can be found. On Windows be certain that the libraries are either in the system path, located in the same directory as the plug-in implementation library, or supplied with Aion. On MacOS, if the library is not a system library, you can either use the install_name_tool to modify your implementation library so it knows where to find the dependencies or you can use the DYLD_LIBRARY_PATH environment variable. On Linux, if you’re not using a library registered using ld.so.conf, you must use the LD_LIBRARY_PATH environment variable.

The Registration Library

The registration library is generally fairly simple, containing nothing more than a handful of data structures as defined in the Aion headers plug_in_data.h, run_time_library_definition.h, and user_function_definition.h. These files can be found in the same location as the Aion libraries used to deploy your algorithm or model as described by Table 22.

The PlugInData Structure

The main structure that defines a plug-in is the PlugInData structure. You must define an instance of this structure using the define PLUG_IN_DATA. On Windows, this structure must also be exported from the registration DLL. You can use the supplied #define named PLUG_IN_EXPORT to do this in a portable manner.

The PlugInData structure references arrays of two other structures, the UserFunctionDefinition structure and the RunTimeLibraryDefinition structure.

Code fragment below demonstrates the use of the PlugInData structure.

#include <plug_in_data.h>

...

extern "C" PLUG_IN_EXPORT PlugInData PLUG_IN_DATA;

extern "C" {
    PLUG_IN_EXPORT PlugInData PLUG_IN_DATA = {
        // The plug-in name
        "http",

        // The plug-in author
        "Inesonic, LLC Development Team",

        // Company
        "Inesonic, LLC",

        // The license tied to the plug-in
        "Copyright 2021,Inesonic, LLC.  "
        "Use under the terms of the Inesonic EULA",

        // A brief description, keep to one line
        "HTTP client plug-in",

        // A detailed description.  You can use some HTML tags
        "<p>"
        "You can use the http client plug-in to issue HTTP get and put methods "
        "to a remote server."
        "</p>"
        "This plug-in supplies the following functions:"
        "</p>"
        "<ul>"
        "<li>HttpGet</li>"
        "<li>HttpPost</li>"
        "</ul>",

        // The plug-in version number
        "1a",

        // Number of functions.
        sizeof(functionDefinitions) / sizeof(UserFunctionDefinition),

        // The function definition structure.
        functionDefinitions,

        // The run-time library definition
        runTimeLibraryDefinition
    };
}

The value functionDefinitions is an array of UserFunctionDefinition instances. The value runTimeLibraryDefinition is an array of RunTimeLibraryDefinition instances.

The RunTimeLibraryDefinition Structure

The RunTimeLibraryDefinition structure is defined in the header run_time_library_definition.h. You will need to create an array of RunTimeLibraryDefinition instances that define the implementation library names and library types.

You should define one entry in the array for each implementation library associated with the plug-in. The last entry in the array should be defined with a null library name.

You should specify the library type using one of the enumeration values LibraryType::CUSTOMER_DYNAMIC_LIBRARY or LibraryType::CUSTOMER_STATIC_LIBRARY.

The sample code below shows how you can use the RunTimeLibraryDefinition structure.

#if (defined(_MSC_VER))

     // The name of the runtime library tied to the function, less the prefix
     // and extension.  On Windows, we prepend a "lib" prefix so that the
     // Windows DLL search algorithm can differentiate between the registration
     // DLL and the implementation DLL.
     #define LIBRARY_NAME ("libhttp")

#else

     // The name of the runtime library tied to the function, less the prefix
     // and extension.
    #define LIBRARY_NAME ("http")

#endif

static const RunTimeLibraryDefinition runTimeLibraryDefinition[] = {
//    libraryName  , libraryType
    { LIBRARY_NAME , LibraryType::CUSTOMER_DYNAMIC_LIBRARY },
    { nullptr      , LibraryType::CUSTOMER_DYNAMIC_LIBRARY }
};

Note

Windows differentiates DLLs by their base name. For this reason, be sure to uniquely name every DLL you create for your plug-ins such that each DLL is named uniquely on your system.

The UserFunctionDefinition Structure

You will need to define an array of UserFunctionDefinition instances with one entry per function. You use this structure to provide details about each function.

For each function you define, you will also need to define an array of function variants using the structure UserFunctionVariant.

The sample code below shows how you can define two functions using the UserFunctionDefinition structure.

static const UserFunctionDefinition functionDefinitions[] = {
    {
        // The internal function name, what you provide here will be mangled.
        "httpGet",

        LIBRARY_NAME,

        // The name the user would use in the document view.
        "HttpGet",

        // The subscript portion of the name as used in the document view.
        "",

        // The backslash command tied to the function
        "httpget",

        // Description displayed in the GUI.
        "Function you can use to perform an HTTP Get operation.",

        // An optional category for the function.  The value is used by the
        // function search functions.
        "networking",

        // Flag indicating if the function requires a random number generator
        // as the first function parameter.  Set to true or false.
        false,

        // Flag indicating if the first parameter after the RNG should be
        // placed as a subscript after the function.  Use this for functions
        // such as log where the base is subscripted.
        false,

        // The number of function variants for this function.
        sizeof(getFunctionVariants) / sizeof(UserFunctionVariant),

        // And the list of variants
        getFunctionVariants
    },
    {
        // The internal function name, what you provide here will be mangled.
        "httpPost",

        LIBRARY_NAME,

        // The name the user would use in the document view.
        "HttpPost",

        // The subscript portion of the name as used in the document view.
        "",

        // The backslash command tied to the function
        "httppost",

        // Description displayed in the GUI.
        "Function you can use to perform an HTTP Post operation.",

        // An optional category for the function.  The value is used by the
        // function search functions.
        "networking",

        // Flag indicating if the function requires a random number generator
        // as the first function parameter.  Set to true or false.
        false,

        // Flag indicating if the first parameter after the RNG should be
        // placed as a subscript after the function.  Use this for functions
        // such as log where the base is subscripted.
        false,

        // The number of function variants for this function.
        sizeof(postFunctionVariants) / sizeof(UserFunctionVariant),

        // And the list of variants
        postFunctionVariants
    }
};

The UserFunctionVariant And UserFunctionParameter Structure

You can use the UserFunctionVariant structure to define all the variants of a given function. For any given function, you should define an array of this structure that defines the returned value of the function, the number of parameters and a pointer to an array of UserFunctionParameter structures that define the type and name of each parameter.

The UserFunctionParameter structures are used to define each parameter required by a function variant, in order. The valueType field indicates the type and the description field provides a description used by the user interface.

The sample code below demonstrates how you can use these structures:

// This is list of parameters for the first variant of the get function
static const UserFunctionParameter getVariant1Params[] = {
//    valueType ,                 description
    { Model::ValueType::TUPLE   , "host"       },
    { Model::ValueType::TUPLE   , "path"       }

};

// This is list of parameters for the second variant of the get function
static const UserFunctionParameter getVariant2Params[] = {
//    valueType                 , description
    { Model::ValueType::TUPLE   , "host"       },
    { Model::ValueType::INTEGER , "port"       },
    { Model::ValueType::TUPLE   , "path"       }
};

// For each function variant, we need to define a list of function parameters
// we accept.  We do this by defining an array of UserFunctionVariant
// instances for each function we're implementing.
static const UserFunctionVariant getFunctionVariants[] = {
    {
        Model::ValueType::TUPLE,                                 // returnValue
        sizeof(getVariant1Params)/sizeof(UserFunctionParameter), // numberParams
        getVariant1Params                                        // parameters
    },
    {
        Model::ValueType::TUPLE,                                 // returnValue
        sizeof(getVariant2Params)/sizeof(UserFunctionParameter), // numberParams
        getVariant2Params                                        // parameters
    }
};