Foreign Function Interface

The Mita language offers the possibility to implement functions which call native C functions in Mita code. The Foreign Function Interface can be used to implement functions in Mita that are not provided by the Mita language or the Mita XDK Platform. This is sometimes necessary, because some algorithms or routines are more easily expressed in C than in Mita. To provide an example for the usage of foreign function interfaces, this chapter will implement the XDK Acoustic Sensor as native C functions in Mita.

  1. Why must Functions be added manually?
  2. Basic Foreign Function Interface Usage
  3. Unchecked vs Checked
  4. Custom C Functions

Why must Functions be added manually?

Mita and C diverge fundamentally in the design of the respective language. Although Mita transpiles into C code, it is not possible to generically call any C function in Mita. For this reason, any C function that is featured in the Mita language or the Mita XDK Platform has a clearly defined Mita signature that adheres to the Mita type system. Any function that the user adds manually in his/her code must also have a clearly defined Mita signature. This often requires that a custom C wrapper for some functionality must be written.

Basic Foreign Function Interface Usage

In many cases, especially with interfaces such as math.h or the stdlib, the functions can be embedded into the Mita code without requiring the user to write a custom C wrapper. For example, Mita does not offer the function int abs(int x) yet, which takes 32-bit signed integer as its input and returns the absolute value of the input.

The function abs() can be embedded and called easily using the following Mita code:

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

every 1 second {
    var x : int32 = -1;
    println(`The absolute value of ${x} is ${abs(x)}`);
}

The declaration of a function that is implemented using the foreign function interface is similar to native Mita function declarations. In addition to the similarities, the keyword native marks this function as a foreign interface function. The keyword unchecked (or alternatively: checked) defines how the native C function receives its input and how it returns a value. The differences between unchecked and checked will be discussed in one of the following chapters. The last keyword header declares which C interface provides access to that function. In the above example, that C interface is the header-file math.h.

Equipped with this syntax, it is possible to implement many of the standard library functions.

Unchecked vs Checked

Taking the example from the previous chapter, we can see that the function declaration for int abs(int x) works as expected. In fact, the declaration in Mita mirrors the declaration in C consistently, with both declarations having the same input and return types. The approach from the previous chapter will work for all C functions that are declared in a similar style.

However, users who are familiar with the BCDS Platform C Implementation will notice that many of the Platform functions are not declared in such a style. In fact, the convention in the BCDS Platform is that of checked functions, where the return value of a function is an Error Code, and not the resulting value. For example, take the following declaration of NoiseSensor_ReadRmsValue():

Retcode_T NoiseSensor_ReadRmsValue(float *rmsValue, uint32_t timeout);

Of course, this can be embedded into Mita using the following foreign function declaration:

native unchecked fn NoiseSensor_ReadRmsValue(rmsValue : &float, timeout: uint32): uint32
    header "XDK_NoiseSensor";

The issue with that approach is the fact that Mita is designed to avoid explicit Return Code handling. This is also the reason why there is no such thing as a Retcode type in Mita, since Mita actually offers exceptions for this.

The keyword checked can be used instead of unchecked, to avoid having to deal with an explicit Return Code, and to let Mita handle throwing exceptions where needed. This keyword tells Mita that the native C function follows the convention of returning an Error Code and providing the actual result of the function as the first argument of the function.

As such the following Mita code will declare the function NoiseSensor_ReadRmsValue() as a checked function:

native checked fn NoiseSensor_readSensor(timeout: uint32): float
    header "XDK_NoiseSensor.h";

With this declaration, the first input of the corresponding C function is now the return value of the Mita function. All subsequent input parameters of the C function are now the inputs of the Mita function. The input parameters of the C function should appear in the same order in the Mita function declaration.

There is one pitfall with this approach. The Mita implementation expects the following general signature for all native C functions that are marked as checked:

Retcode_T my_function(type *result, type input1, type input2);

Unfortunately, this general signature does not account for the case where there is no expected result for a native C function, such as the following:

Retcode_T NoiseSensor_Setup(uint32_t samplingFrequency);

This function returns an error code, but it does not return a significant value, which was the case for NoiseSensor_readSensor(). Naively, to account for the error code, this native C function could be embedded in Mita as follows:

native checked fn NoiseSensor_Setup(samplingFrequency: uint32): void
    header "XDK_NoiseSensor.h"

With this implementation, the Mita code will transpile to the following C code:

exception = NoiseSensor_Setup(NULL, 10);

With the checked foreign function declaration, Mita expects a function NoiseSensor_Setup() with the signature Retcode_T NoiseSensor_Setup(void *result, uint32_t samplingFrequency) This will throw an error, since there exists no function NoiseSensor_Setup() with two input parameters.

To avoid this issue, functions can be wrapped in C implementation files in the way that Mita expects.

Custom C Functions

Upon transpilation of the Mita code, all C source- and header files in the root directory of the Mita project are included in the project’s generated Makefile. This allows the user to add C files with custom C code (and corresponding header files) in the root-directory of the project. These custom C functions can then be used in the Mita code after embedding them as foreign functions. Using the acoustic sensor as an example, the following few steps will define custom C functions that allow initializing and reading the acoustic sensor in a Mita project.

The implementation will consist of one function for setting the acoustic sensor up and starting it, one function for reading RMS values, and one function for stopping the acoustic sensor.

Copy the following code into a file called FFI_NoiseSensor.c in the root directory of your Mita project:

#include "FFI_NoiseSensor.h"
#include "XDK_NoiseSensor.h"

Retcode_T noise_setup(void *result, uint32_t samplingFrequency) {
    Retcode_T ret;
    ret = NoiseSensor_Setup(samplingFrequency);
    if(ret == RETCODE_OK) {
        ret = NoiseSensor_Enable();
    }
    return ret;
}

Retcode_T noise_read(float *result, uint32_t timeout) {
    Retcode_T ret;
    ret = NoiseSensor_ReadRmsValue(result, timeout);
    return ret;
}

Retcode_T noise_stop(void *result) {
    Retcode_T ret;
    ret = NoiseSensor_Disable();
    if(ret == RETCODE_OK) {
        ret = NoiseSensor_Enable();
    }
    return ret;
}

This code will allow us to use the functions in Mita in a checked way, as discussed in the previous chapter. Since Mita is going to handle exceptions, we do not need to handle Error Codes if they are not equal to RETCODE_OK.

To be able to embed these functions in Mita, we need an header file that exposes the implemented functions.

Copy the following code into a file called FFI_NoiseSensor.h in the root directory of your Mita project:

#ifndef FFI_NOISESENSOR_H_
#define FFI_NOISESENSOR_H_

#include "BCDS_Retcode.h"
#include "BCDS_Basics.h"

Retcode_T noise_setup(void*, uint32_t);
Retcode_T noise_read(float*, uint32_t);
Retcode_T noise_stop(void*);

#endif /* FFI_NOISESENSOR_H_ */

With this, we’re able to embed the functions in Mita as follows:

native checked fn noise_setup(samplingFrequency: uint32): void header "FFI_NoiseSensor.h";
native checked fn noise_read(timeout: uint32): float header "FFI_NoiseSensor.h";
native checked fn noise_stop(): void header "FFI_NoiseSensor.h";

These functions can now be used in Mita.