Calls to external code are useful in three situations:
Among examples belonging to the third case, one can mention updating the parameters of a real-time controller running with a real process, collecting experimental data to obtain a model, or changing the coefficients of an audio filter to add a new dimension to what the user perceives.
Calls to external code are performed by calling functions in a shared library, also known as dynamic link library. Several shared libraries can be used simultaneously, and each of them can contain several functions. Each function must have the following prototype, given here in ISO (ANSI) C using the header file LME_Ext.h. Other languages can also be used, provided that the same calling conventions are used. In C++, for instance, prototypes must be preceded by extern "C" to disable name mangling.
lme_err fn(lme_ref lme, lme_int nargin, lme_int nargout)
Its three arguments are:
The output of the function is 1 for success or 0 for failure.
Retrieving the value of the input arguments and setting the output arguments are performed with the help of callback functions (i.e. functions implemented in LME which are called back by the extension; the header file LME_Ext.h hide the implementation details). Currently, arguments can be real or complex matrices, arrays of any dimension and type supported by LME, strings, lists, structures, structure arrays, and binary objects. Callback functions which manipulate arguments return 1 if the call is successful, or 0 otherwise. Failures are not fatal; for example, if a string or numeric argument is expected, you may try to retrieve a string, then try to get a number (or a numeric matrix) if it fails.
Input arguments can be retrieved in any order. Output arguments must be pushed in reverse order, beginning with the last one; or in normal order from first to last if LMECB_ReverseOutputArguments is called once all the arguments have been pushed. Exactly nargout values must be pushed.
Here is a list of callback functions to get input arguments, set output arguments, throw errors, and output information.
Enum | Value | Object type |
---|---|---|
k_lme_obj_unknown | 0 | Unknown (other) |
k_lme_obj_array | 1 | Array of any type |
k_lme_obj_list | 2 | List |
k_lme_obj_struct | 3 | Structure |
k_lme_obj_structarray | 4 | Structure array |
Functions are added to the set of built-in functions when LME starts up. They effectively extend LME. To permit LME to load them, you must provide the following function, named InstallFn:
lme_int InstallFn(lme_ref lme, lme_fn **fnarray) { /* initialize any resource necessary for the functions */ *fnarray = [array of function descriptions]; return [number of elements in *fnarray]; }
The essential purpose of this function, which must be exported with whatever mechanism is available on your platform (__declspec(dllexport) for DLL on Windows or the PEF export options on Mac OS 9), is to refer LME to an array of function descriptions. This array, which is typically defined as static, has elements of type lme_fn:
typedef struct { char name[32]; lme_extfn fn; lme_int minnargin, maxnargin; lme_int minnargout, maxnargout; } lme_fn;
The field name is the name of the function (what you will use in your SQ files), fn is a pointer to the function which implements the behavior of the function, minnargin and maxnargin are the minimum and maximum number of input arguments your function is ready to accept, and minnargout and maxnargout are the minimum and maximum number of output arguments your function is ready to provide. Typically, if your function can provide output argument(s), you should set minnargout to 1; LME will display a result if you omit the semicolon at the end of a call to your function.
You can implement new types of object (binary objects), at most one per extension. Functions can overload existing functions in a similar way as for objects defined with class. Overloaded functions must begin with the prefix lme_k_binary_overload_str_prefix; for example to define a function plus for the addition of your binary objects, the entry in the array of functions would be
{lme_k_binary_overload_str_prefix "plus", overloadedPlus, 2, 2, 1, 1}
This prefix must not be used for functions unless they take binary objects as input arguments. See the example 3 below for a complete example.
You can also allocate resources in InstallFn (such as opening files); in that case, you want to define and export a function named ShutdownFn to release these resources when LME terminates:
void ShutdownFn(lme_ref lme) { /* release all resources allocated by InstallFn */ }
The following extension adds two functions to LME: plus1 which accepts up to 50 double real or complex matrix arguments and return them in the same order with 1 added, and hi which displays a message if there is no output argument, or returns it as a string if there is one.
#include "LME_Ext.h" #include <string.h> static lme_err plus1(lme_ref lme, lme_int nargin, lme_int nargout) /* same as (x+1), but with multiple arguments */ { int i, j; lme_float *re, *im, *re1, *im1; lme_err status; lme_int m, n; for (i = nargout; i >= 1; i--) /* backward */ { if (i <= nargin) { status = LMECB_GetMatrix(i, &m, &n, &re, &im); if (!status) return 0; status = LMECB_PushMatrix(m, n, &re1, im ? &im1 : 0); if (!status) return 0; for (j = 0; j < m * n; j++) re1[j] = re[j] + 1; if (im) for (j = 0; j < m * n; j++) im1[j] = im[j]; } else if (!LMECB_PushMatrix(0, 0, &re1, 0)) return 0; } return 1; } static lme_err hi(lme_ref lme, lme_int nargin, lme_int nargout) /* hello world */ { char *msg = "Hello, World!"; if (nargout == 1) { lme_string8 str; int i; if (!LMECB_StartPushString(strlen(msg), &str)) return 0; for (i = 0; i < strlen(msg); i++) /* without the '\0' */ str[i] = msg[i]; if (!LMECB_EndPushString()) return 0; } else LMECB_DbgWriteStr(msg); return 1; } static lme_fn fn[] = { {"plus1", plus1, 0, 50, 0, 50}, {"hi", hi, 0, 0, 0, 1} }; lme_int InstallFn(lme_ref lme, lme_fn **fnarray) { LMECB_DbgWriteStr("Installing test functions."); *fnarray = fn; return 2; }
The extension below implements displayobject which displays the skeleton of its input argument. It shows how to scan all elements of a list or a structure.
#include "LME_Ext.h" #include <stdio.h> static lme_err displayRec(lme_ref lme, lme_object *o) /* called recursively */ { lme_int status = 1, ndims, *size, len, i; lme_object el; lme_char8 str[k_lme_fieldname_maxlength]; switch (o->objtype) { case k_lme_obj_unknown: LMECB_Write(1, "unknown", -1, 1); break; case k_lme_obj_array: LMECB_Write(1, "array(", -1, 1); status = LMECB_ObjectToArray(o, &ndims, &size, NULL, NULL, NULL); if (!status) return 0; for (i = 0; i < ndims; i++) { sprintf(str, i > 0 ? "x%d" : "%d", size[i]); LMECB_Write(1, str, -1, 1); } LMECB_Write(1, ")", -1, 1); break; case k_lme_obj_list: LMECB_Write(1, "{", -1, 1); status = LMECB_ObjectLength(o, &len); for (i = 1; status && i <= len; i++) { if (i > 1) LMECB_Write(1, ",", -1, 1); status = LMECB_GetElementFromListObject(o, i, &el); if (status) status = displayRec(lme, &el); } LMECB_Write(1, "}", -1, 1); break; case k_lme_obj_struct: LMECB_Write(1, "struct(", -1, 1); status = LMECB_ObjectLength(o, &len); for (i = 1; status && i <= len; i++) { if (i > 1) LMECB_Write(1, ",", -1, 1); status = LMECB_GetFieldNameFromStructObject(o, i, str); if (!status) break; LMECB_Write(1, str, -1, 1); LMECB_Write(1, "=", -1, 1); status = LMECB_GetFieldFromStructObject(o, str, &el); if (status) status = displayRec(lme, &el); } LMECB_Write(1, ")", -1, 1); break; } return status; } static lme_err displayobject(lme_ref lme, lme_int nargin, lme_int nargout) /* display argument */ { lme_object o; if (!LMECB_GetObject(1, &o) || !displayRec(lme, &o)) return 0; LMECB_Write(1, "\n", -1, 1); return 1; } static lme_fn fn[] = { {"displayobject", displayobject, 1, 1, 0, 0} }; lme_int InstallFn(lme_ref lme, lme_fn **fnarray) { LMECB_DbgWriteStr("Installing displayobject."); *fnarray = fn; return 1; }
The extension below implements a new type for integer arithmetic modulo n. The function modint(i,n) creates a new object of this type. Operators +, - (binary and unary), and * are overloaded to support expressions like modint(2,7)*3+5, whose result would be 4 (mod 7). The function disp is also overloaded; it can be called explicitly, but also implicitly to display the result of an expression which is not followed by a semicolon.
#include "LME_Ext.h" #include <stdio.h> typedef struct { long i, n; } Data; static lme_err modint(lme_ref lme, lme_int nargin, lme_int nargout) // modint(i, n) -> create a binary object // for arithmetic modulo n { lme_float x, y; Data *result; if (!LMECB_GetScalar(1, &x, NULL) || !LMECB_GetScalar(2, &y, NULL)) return 0; if (!LMECB_PushBinaryData(sizeof(Data), (void **)&result)) return 0; result->n = (long)y; result->i = (long)x; return 1; } static lme_err getTwoArgs(lme_ref lme, Data *data1, Data *data2) // get two numbers with at least one binary object { Data *d; lme_float x; if (LMECB_GetBinaryData(1, NULL, (void **)&d)) { *data1 = *data2 = *d; if (LMECB_GetBinaryData(2, NULL, (void **)&d)) { // binary, binary *data2 = *d; return 1; } else if (LMECB_GetScalar(2, &x, NULL)) { // binary, scalar data2->i = (long)x; return 1; } else return 0; } else { // 1st arg is not binary, hence 2nd should be if (LMECB_GetBinaryData(2, NULL, (void **)&d)) *data1 = *data2 = *d; else return 0; if (!LMECB_GetScalar(1, &x, NULL)) return 0; data1->i = (long)x; return 1; } } static lme_err plus(lme_ref lme, lme_int nargin, lme_int nargout) // overloaded operator + for arithmetic modulo n { Data data1, data2, *result; if (!getTwoArgs(lme, &data1, &data2) || !LMECB_PushBinaryData(sizeof(Data), (void **)&result)) return 0; result->n = data1.n; result->i = (data1.i + data2.i) % data1.n; return 1; } static lme_err minus(lme_ref lme, lme_int nargin, lme_int nargout) // overloaded operator - for arithmetic modulo n { Data data1, data2, *result; if (!getTwoArgs(lme, &data1, &data2) || !LMECB_PushBinaryData(sizeof(Data), (void **)&result)) return 0; result->n = data1.n; result->i = (data1.n + data1.i - data2.i) % data1.n; return 1; } static lme_err mtimes(lme_ref lme, lme_int nargin, lme_int nargout) // overloaded operator * for arithmetic modulo n { Data data1, data2, *result; if (!getTwoArgs(lme, &data1, &data2) || !LMECB_PushBinaryData(sizeof(Data), (void **)&result)) return 0; result->n = data1.n; result->i = (data1.i * data2.i) % data1.n; return 1; } static lme_err uminus(lme_ref lme, lme_int nargin, lme_int nargout) // overloaded unary operator - for arithmetic modulo n { Data *data, *result; if (!LMECB_GetBinaryData(1, NULL, (void **)&data) || !LMECB_PushBinaryData(sizeof(Data), (void **)&result)) return 0; result->n = data->n; result->i = (data->n - data->i) % data->n; return 1; } static lme_err disp(lme_ref lme, lme_int nargin, lme_int nargout) // overloaded "disp" function to display binary object { Data *data; char str[64]; if (!LMECB_GetBinaryData(1, NULL, (void **)&data)) return 0; sprintf(str, "%ld (mod %ld)\n", data->i, data->n); LMECB_Write(1, str, -1, 1); return 1; } static lme_err subsref(lme_ref lme, lme_int nargin, lme_int nargout) // overloaded subsref (field access) to get // the contents of binary object // b.i === subsref(b, {type='.',subs='i'}) -> value // b.n === subsref(b, {type='.',subs='n'}) -> modulo { Data *data; lme_object o, fld; unsigned short *str; lme_int ndims, *size, nbytes, type; lme_float *res; if (!LMECB_GetBinaryData(1, NULL, (void **)&data) || !LMECB_GetObject(2, &o)) return 0; // extract field name from 2nd arg if (!LMECB_GetFieldFromStructObject(&o, "type", &fld) || !LMECB_ObjectToArray(&fld, &ndims, &size, &nbytes, &type, (void **)&str) || ndims != 2 || size[0] * size[1] != 1 || type != k_lme_type_char || (char)str[0] != '.' || !LMECB_GetFieldFromStructObject(&o, "subs", &fld) || !LMECB_ObjectToArray(&fld, &ndims, &size, &nbytes, &type, (void **)&str) || ndims != 2 || type != k_lme_type_char) return LMECB_Error("LME:wrongType", NULL); if (size[0] * size[1] != 1 || (char)str[0] != 'i' && (char)str[0] != 'n') return LMECB_Error("LME:undefField", NULL); // push result if (!LMECB_PushMatrix(1, 1, &res, NULL)) return 0; *res = (char)str[0] == 'i' ? data->i : data->n; return 1; } static lme_fn fn[] = { {"modint", modint, 2, 2, 1, 1}, {lme_k_binary_overload_str_prefix "plus", plus, 2, 2, 1, 1}, {lme_k_binary_overload_str_prefix "minus", minus, 2, 2, 1, 1}, {lme_k_binary_overload_str_prefix "mtimes", mtimes, 2, 2, 1, 1}, {lme_k_binary_overload_str_prefix "uminus", uminus, 1, 1, 1, 1}, {lme_k_binary_overload_str_prefix "disp", disp, 1, 1, 0, 0}, {lme_k_binary_overload_str_prefix "subsref", subsref, 2, 2, 1, 1} }; lme_int InstallFn(lme_ref lme, lme_fn **fnarray) { LMECB_DbgWriteStr("modint: modint, disp, minus, mtimes, plus, " "subsref, uminus"); *fnarray = fn; return 7; }
We have three suggestions to make the development of your external functions easier: