CFFI (‘C Foreign Function Interface’) is a way of linking native code into a non-native VM, in this case Hxcpp.
From the very beginning, Hxcpp supported a CFFI that was very similar to the neko CFFI. This was no accident, because it allowed me to make good use the neko library code.
The actual header file for the Hxcpp CFFI is quite different from the neko one – mainly because the neko one assumes a particular memory layout for the ‘value’ type, while the hxcpp one treats the ‘value’ type as opaque. This allows code written against the Hxcpp header file to be used by neko and Hxcpp, while the neko header restricts the implementation to neko.
All function arguments and return values in the original version of CFFI are of type “value”. The ‘value’ type is implemented with a pointer. So to pass, say, an Int or String to a CFFI function, it must be “boxed” by allocating a wrapper object. This allows anything to be passed, but incurs an overhead of allocation, and must be extracted via a function like “val_int”.
To solve this problem, “CFFI Prime” was written last year (“prime” because the macro “DEFINE_PRIM” (short for “define primitive”) became “DEFINE_PRIME” (short for “define awesomeness”) ).
As far as writing the c++ interface code, this simply offers syntax sugar to convert the ‘value’ types to the appropriate c++ types using the power of c++ typing. eg, compare the classic:
value sum(value a, value b) { if( !val_is_int(a) || !val_is_int(b) ) return val_null; return alloc_int(val_int(a) + val_int(b)); } DEFINE_PRIM(sum,2); // function sum with 2 arguments
with:
int addInts(int a, int b) { return a+b; } DEFINE_PRIME2(addInts);
Both implementations create a function that can be dynamically loaded and called by neko or Hxcpp, but the second one us just easier to write. If you want to use a “complex” type (one that is not int, float, double, bool) then you still use “value” and the associated value functions as before. String has its own special type, HxString, in CFFI Prime, and “const char *” can be used.
The Prime code has a second advantage – it exposes the actual function pointer (in this case “addInts”) which can be loaded into one of Hxcpp’s native “cpp.Function” objects. This can then be called directly from Hxcpp giving super-fast and no-boxing native calls. You still need to use the “value” type to pass complex objects, since this decouples the implementation of these objects from the API for accessing them. This is how the same native code can be used by multiple run times.
The function pointers are exposed by exporting two symbols for each “DEFINE_PRIM” define – one just for hxcpp, and one for general use. To load the function, you use a java-style signature in haxe, which gets converted by a haxe macro to perform two operations: 1, to type-check the signature against the signature generated by the “DEFINE_PRIME” macro, and 2, to correctly type the cpp.Function pointer in haxe code, so the correct code can be output, and the haxe compiler can check your arguments in haxe.
static var add = Loader.load("addInts", "iii" ); ... assertTrue(add!=null); assertEquals(7, add.call(2,5));
If you try to pass a non-integer to the addInts function, the haxe compiler will complain. If the signature of addInts does not match, then you will get a run-time error when you load the dll. Notice also the “.call” to call the function. The need for this may be eliminated by haxe’s pseudo-operator-overloading-via-abstract.
The Loader macro is actually not that complicated, and it is possible to write your own version that uses an alternative to the java-style signature. Using the magic of haxe, it should also be able to infer then signature of the “addInts” from the “add” function.
Features from the unit tests should be safe to use in your own code.