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.
Platform |
Location |
---|---|
Windows |
|
Linux |
|
MacOS |
|
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.
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.
Figure 108 shows the required plug-in directory structure for each plug-in on MacOS installations.
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
}
};