Foreign Function Interface

XDK LIVE transpiles to C code, i.e. the compiler produces C code rather than a binary executable. This begs the question if we can call existing C code from within XDK LIVE programs. Such integration of the “target language” is referred to as foreign function interface (or FFI in short) because the functions we wish to call from within XDK LIVE are defined in a foreign language: C. Other languages sport similar concepts, for example TypeScript supports declarations which allow you to use code written in JavaScript (the language TypeScript compiles to).

Calling native C functions is useful if we want to re-use existing libraries, or for the things which are easier to express in C than they are to express in XDK LIVE. Suppose you wanted to connect a new sensor to your device and the sensor came with a driver library written in C. Using the foreign function interface you can integrate and use that driver library from within XDK LIVE.

Native Functions

To make a function written in native C known to XDK LIVE you have to declare them in an XDK LIVE file, e.g.:

native unchecked fn abs(n : int32) : int32
    header "math.h";

The header which we have named during the declaration of the native function will be automatically included in the generated C code where necessary. With the abs function declared like that we can use it just like a regular XDK LIVE function.

package main;
import platforms.xdk110;

every 1 second {
    let xAxisAcceleration = acc
    println(`Absolute X axis acceleration: ${abs(accelerometer.x_axis.read())}`);
}

Unchecked vs Checked

Notice the unchecked keyword in the example above. XDK LIVE supports exceptions which map to special return values in the generated C code. The unchecked keyword and its counterpart checked tell the compiler if it should use the XDK LIVE calling convention which handles exceptions, or the C calling convention where the return value is not used for exceptions.

Let’s look at both calling conventions in detail. For both calling conventions we will see the XDK LIVE declaration and the corresponding C header that the compiler expects.

Unchecked

native unchecked fn foobar(n : int32) : int32
    header "unfoobar.h";
    
native unchecked fn foobarWithRef(ref : &int32) : int32
    header "unfoobar.h";
    
native unchecked fn foobarVoid(n : int32) : void
    header "unfoobar.h";

expects unfoobar.h to look something like

#include <stdint.h>

int32_t foobar(int32_t n);

int32_t foobarWithRef(int32_t* ref);

void foobarVoid(int32_t n);

Checked

native checked fn foobar(n : int32) : int32
    header "foobar.h";
    
native checked fn foobarWithRef(ref : &int32) : int32
    header "foobar.h";
    
native checked fn foobarVoid(n : int32) : void
    header "unfoobar.h";

expects foobar.h to look something like

#include <BCDS_Basics.h>
#include <BCDS_Retcode.h>
#include <stdint.h>

Retcode_T foobar(int32_t* result, int32_t n);

Retcode_T foobarWithRef(int32_t* result, int32_t* ref);

Retcode_T foobarVoid(void* result, int32_t* ref);

The Retcode_T and BCDS_* includes are specific to the XDK110 platform. Other platforms will use a different type to express error codes.

Custom C file include

The Makefile of an XDK LIVE project is automatically generated. The compiler will include all C files and header in your project in that Makefile. This way you can mix C and XDK LIVE code within the same project. For example you could have a custom header and corresponding implementation in your project, and an XDK LIVE file which declares them.

Using custom C code

Pitfalls

In order to support all features of XDK LIVE, we sometimes have to generate code which does not directly map to a “native C API”. Arrays are a good example: each array type in XDK LIVE gets its corresponding C type. For example array<int32> turns into an array_int32 structure and not int32_t[] as one could expect.

Whenever you cannot import an existing API directly using unchecked native functions, you could write a wrapper in C using custom C file includes described above.