OxFFI (version 0.1)


Initial doc: July 2006, by John Zedlewski (johnzed at gmail dot com)


Intro to OxFFI

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

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

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"


License

OxFFI is licensed under the GNU LGPL.


Basic usage:


A simple example:

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!");

Dealing with matrices:

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.


Auto-vectorizing functions

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];
}

More examples

See the packages/oxffi/samples directory. demo_gslffi.ox has nontrivial examples for the GNU scientific library that may be helpful.


Limitations:


Variable type strings and descriptions:

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