Initial doc: July 2006, by John Zedlewski (johnzed at gmail dot com)
OxFfi is a "foreign function interface" that allows Ox programs to easily call compiled code in dynamic libraries without the need to write and compile wrappers.
To call a C function with oxffi, you just tell oxffi the name and argument types of the function, and oxffi returns an object that you can call just like a normal Ox function.
Requirements for users (who just want to use the precompiled binaries):
32-bit x86 Linux distribution (tested on Ubuntu 6.06)
Ox 4.x
Installing OxFfi:
unzip the archive under your ox/packages directory (so you have "ox/packages/oxffi")
test the installation by changing into the "ox/packages/oxffi/samples" directory and running "oxl test_oxffibasic.ox"
All useful oxffi functions are exported in <packages/oxffi/OxFfi.h>
There are two interfaces in oxffi: the class-based interface, and FfiSimpleFunc. I'll describe FfiSimpleFunc first, since it's easiest:
FfiSimpleFunc(const sDllname,
const sFuncname,
const asArgTypes,
const sRettype) |
sDllname is the name of the library containing the function, so it'll look something like "libgsl.so" sFuncname is the plain function name as a string - aArgs is an array with one entry per argument to the function. Each element is a string that describes a parameter type for the underlying function. For instance, an array like { "int", "double" } would describe a function whose first parameter is an integer and whose second param is a double.
You can see a list of all supported parameter types by calling OxffiShowVarTypes() from Ox, or by looking at the end of this README file.
sRettype is a single type descriptor element, like those used in asArgTypes. Functions that return newly-allocated matrices are a little more complicated, so I'll describe them later.
FfiSimpleFunc returns a callable function, which works just like any other Ox function.
Let's say we want to call the function fnmatch from the GNU C library. The function looks like:
int fnmatch(char *s1, char *s2, int flags) |
and it returns 1 if the string s2 matches the pattern in s1.
To create the wrapper we just do:
decl fnmatch = FfiSimpleFunc("libc.so.6", "fnmatch",
{"string", "string", "int"}, "int"); |
(Note that "libc.so.6" is the dll name for the c library.) Now we can call this in Ox like a normal function:
if (fnmatch("a*b", "aaab")) println("matched!"); |
C and fortran programs have various ways of defining a matrix. The only format supported by oxffi so far is a C-style "double**" matrix, the same format used internally by Ox. Note that C functions generally need to be told the dimensions of the matrix in some other argument, so it's common to have a function like:
double doMatStuff(double **m, int r, int c) |
And you'd have to manually specify the rows and columns in Ox:
decl FmatStuff = FfiSimpleFunc("mylib.so", "doMatStuff",
{"cmatrix", "int", "int"}, "double");
decl mTest = zeros(3,3);
FmatStuff(mTest, rows(mTest), columns(mTest)); |
Changes made to these matrices within the C code are NOT propagated back to Ox. You need to use the "cmatrix*" type to allow the C code to change the matrix, and the callers then need to pass in an address of a matrix, like: "FotherMatStuff(&mTest, r, c)"
Right now, only row vectors can be easily converted to a "double *" array format. Column vectors should either be transposed in Ox, or passed in as "double**" matrices.
If a function returns a newly-allocated matrix or vector, things get tricker, because Ox needs to know the size of the returned object. In this case, instead of using a return type like "cmatrix", you need to pass in a "matrix sizer" object to FfiSimpleFunc, e.g. an instance of FfiMatOutFixedSize. For example, if we have a function "double **makeMat(double x)" in C that returns a 3x3 matrix, we could write (in Ox):
decl sizer33 = new FfiMatOutFixedSize(3,3);
decl FmakeMat = new FfiSimpleFunc("mylib.so", "makeMat",
{ "double" }, sizer33);
decl mRes = FmakeMat(1.0); |
and mRes will now contain the matrix returned by the C function. It's also handy to use a FfiMatOutCloneSize object, which tells oxffi that the returned matrix will have the same size and shape as some matrix argument. You tell FfiMatOutCloneSize the argument number when you create the object, so you could do something like:
decl sizeBasedOnFirstArg = new FfiMatOutCloneSize(0);
decl FcloneMat = new FfiSimpleFunc("mylib.so", "cloneMat",
{ "cmatrix", "int", "int" },
sizeBasedOnFirstArg);
decl inMat = zeros(4,4);
decl mRes = FcloneMat(inMat, rows(inMat), columns(inMat)); |
Now, mRes will hold a 4x4 matrix, because we've based its size on the first argument (passing 0 to the constructor indicates the first argument, since Ox indexes starting from 0); See OxFfi.h for more information on these sizers, plus equivalent ones for vectors.
Ox functions typically work on whole vectors at a time, while it's common to write C functions that only operate on individual scalars. Oxffi allows you to "auto vectorize" a function, so that the oxffi library will make a scalar function act like a vector one by calling it repeatedly, once with each element of the vector. This is both cleaner and faster than doing the loop in Ox.
To auto_vectorize a function, just use "double_vectorize" for the argument type where you would normally use "double", and do the same for the return type. You can keep the return type as a scalar, if you prefer, in which case the returned value comes from the last function call.
If you auto-vectorize multiple arguments, they must ALL have the same length.
Example: consider a C function:
double add(double x, double y) { return x + y } |
Ox code:
decl FaddVec = FfiSimpleFunc("mylib.so", "add",
{"double_vectorize", "double_vectorize"},
"double_vectorize);
decl vX = <1,2,3>, vY = <5, 6, 7>, vRes;
vRes = FaddVec(vX, vY); // loop done in oxffi code automatically |
This produces the same result as: "vRes = vX + vY;" and it's equivalent to (but faster than) this non-vector version using the same C function:
decl FaddScalar = FfiSimpleFunc("mylib.so", "add",
{"double", "double"},
"double");
decl vX = <1,2,3>, vY = <5, 6, 7>, vRes = zeros(vX);
decl i;
for (i=0; i < 3; i++) {
vRes[i] = vX[i] + vY[i];
} |
See the packages/oxffi/samples directory. demo_gslffi.ox has nontrivial examples for the GNU scientific library that may be helpful.
This is an early release with many limitations (which will hopefully) be fixed eventually.
The only platform supported now is x86 32-bit Linux.
Cannot pass Ox functions as arguments.
No support for access to C structs.
You can't call varargs functions.
Better error handling is needed.
Type string | Conversion |
double | Ox double -> C double |
int | Ox int -> C int |
uint | Ox int -> C unsigned int |
string | Ox string -> C NUL-terminated char* |
int* | Ox address of int (like &iVal)-> C in/out int |
vector* | Ox address of vector -> C in/out double* |
cmatrix* | Ox address of matrix -> C in/out double** |
vector | Ox matrix -> C double * (changes not returned to Ox) |
cmatrix | Ox matrix -> C double ** |
pointer | Ox int -> C void* |
void | (return type only) C void -> nothing |
double_vectorize | Ox vector -> auto-vectorization around C double |