ffi
FFI is provided on a platform/plugin basis because it ties into the underlying system achitecture, something the core is not supposed to do. The server plugin offers a compliant ffi/blob implementation. By default the core connects to the target platform through stdin/stdout, limited file i/o services and shell (Program os). Auxiliary integration is provided by ffi/blobs through application centered plugins like the server plugin.
FFI stands for Foreign Function Interface. It allows you to use functionality written by others in different programming languages, provided through DLL files, SO files, or Dylib files. These could be a variety of functions, and there’s a vast range of available functionality through this method. Let’s start with an example:
Server link: (
List new ;
['/usr/lib/x86_64-linux-gnu/libc.so.6'] ;
['printf'] ;
( List new ; ['pointer'] ; ['int'] ) ;
['void'] ;
['Printf'] ;
['template:number:']
).
>> s := Blob utf8: ['FFI has %d letters.\n'].
Printf template: s number: 3.
s free.
The result of this code is that you’ll see the following on the command line: FFI has 3 letters. Now, you’re probably thinking, “That’s a lot of code for something so simple.” I mean, couldn’t we just solve this with a Gui show command? The answer is a resounding yes! But this is meant as an illustrative example. FFI is usually used for more complex tasks, but those don’t make good examples, so I chose something trivial.
The arguments for link method:
- Argument #1: The DLL or SO file you want to use.
- Argument #2: The function in that file you want to link.
- Argument #3: A sequence with the names of the data types of the function’s arguments.
- Argument #4: The name of the function’s return type.
- Argument #5: The name of the object you want to link this function to (if it doesn’t exist, it will be created automatically).
- Argument #6: The message that this function should be linked to.
In the example above, we want to link the printf function from libc.so. You can find the data type names in the documentation of the software you are linking to. The available types are: void, pointer, float, double, int, uint, char, uchar, intX, and uintX, where X can be 8, 16, 32, or 64.
These types refer to the number of bytes required to store the data. For printf, we are linking to a new object called Printf and the message template:number:. You translate the external function into the Xoscript dialect before using it. The message template:number: expects a buffer with the template text as its first parameter. We create this buffer using a Blob object, which allows you to manually allocate memory. You are responsible for freeing this memory afterward with the free message.
You can fill a memory blob in various ways. In our example, we fill it with text, so we use the utf8: message (UTF-8 is an encoding to convert text into bytes). You can also fill a blob with bytes:, passing a list of byte values. To read the contents of a blob, use from:length:. You’ll get the bytes back as a sequence. You can even create a C-struct with a Blob using the struct: message, passing a sequence of C types. This may be necessary when calling a C function in an external software library that expects a pointer to a struct.
{warning} You need to free blobs yourself. Blobs are not cleared by the garbage collector. {/warning}
Since blobs can be exchanged with external functions from libraries, they cannot be garbage collected automatically (they might remain in use by external functions or these external functions might want to free them themselves). Therefore you need to free blobs yourself if necessary.
To free a blob use:
myblob free.
To free struct use:
mystruct structfree free.
So for a struct, you need to send two messages: structfree and free to deallocate the memory block.
[ Blob ] bytes: [ List ]
Example:
Server init.
# create a memory block of 10 bytes
>> memory := Blob new: 10.
# fill it with 3 bytes
memory bytes: ( List new ; 65 ; 66 ; 67 ).
# write the bytes to StdOut as a string
Out write: memory string, stop.
# free the memory block
memory free.
Result:
[ Blob ] new: [ Number ]
Example:
Server init.
# create a memory block of 10 bytes
>> memory := Blob new: 10.
# fill it with 3 bytes
memory bytes: ( List new ; 65 ; 66 ; 67 ).
# write the bytes to StdOut as a string
Out write: memory string, stop.
# free the memory block
memory free.
Result:
[ Blob ] utf8: [ String ]
Example:
Server init.
# create a blob from an utf8 string
>> a := Blob utf8: ['hello 123'].
Out write: a, stop.
a free.
Result:
[ Blob ] new: [ Number ] type: [ String ].
Example:
Server init.
>> x := Blob new: 65 type: ['char'].
# for this test you need set a path to libc (puts)
>> libname := Program setting: ['FFITESTLIB'].
Server link: (
List new ;
libname ;
['putchar'] ;
( List new ; ['char'] ) ;
['void'] ;
['Print'] ;
['char:']
).
Print char: (x from: 0 length: 1), stop.
x free.
Result:
[ Blob ] from: [ Number ] length: [ Number ]
Example:
Server init.
>> x := Blob utf8: ['from utf8 string'].
x from: 0 length: 10, each: { :i :b
Out write:
['byte #'] + i + [' is: '] + b,
stop.
}.
x free.
Result:
[ Blob ] struct: [ List ]
Example:
Server init.
# assumes int = 32 bit ( 4 bytes )
>> a := Blob new: 4 * 9.
a bytes: (
List new
; 1 ; 0 ; 0 ; 0
; 10 ; 0 ; 0 ; 0
; 12 ; 0 ; 0 ; 0
; 5 ; 0 ; 0 ; 0
; 0 ; 0 ; 0 ; 0
; 81 ; 0 ; 0 ; 0
; 0 ; 0 ; 0 ; 0
; 0 ; 0 ; 0 ; 0
; 1 ; 0 ; 0 ; 0
).
>> struct := Blob struct: (
List new
; ['int']
; ['int']
; ['int']
; ['int']
; ['int']
; ['int']
; ['int']
; ['int']
).
>> libname := Program setting: ['FFITESTLIB'].
Server link: (
List new ;
libname ;
['asctime'] ;
( List new ; ['pointer'] ) ;
['pointer'] ;
['Time'] ;
['asctime:']
).
Out write: (Time asctime: a), stop.
a free.
struct structfree.
struct free.
Result: