Foreign functions

F

Writing interfaces to C libraries

Introduction

Kaya can call functions in external C libraries via a simple, but fairly unsafe, foreign function call mechanism. The basic idea is to give a Kaya type to each external function you wish to use. Foreign functions are listed in a foreign "file" {...} block; such blocks give the foreign function name along with their Kaya type. The "file" is currently unused; it was originally intended to help with dynamic loading, but may be removed in future. You can use it as a comment to remind you where these functions come from.

The Kaya compiler knows how to convert its primitive types into C types (and back), so any C function with a simple interface can be called directly. Some examples can be found in the Maths library, which simply calls C library functions directly, eg:

foreign "libm" {
    ...
    public Float sin(Float x) = sin;
    public Float cos(Float x) = cos;
    public Float tan(Float x) = tan;
    ...
}

When doing this, bear in mind that Char and String correspond to the C types wchar_t and wchar_t*not char and char*. It may therefore be useful to write a glue function. This is an example from the standard library:

KString do_getenv(const KString env) {
  return KSTRING(getenv(CSTRING(env)));
}

KString is defined in the Kaya Value API as a Kaya String, and the KSTRING and CSTRING macros convert between this and char*.

Aside from primitives, Kaya can convert values with an array type to and from C types. In this case, the corresponding argument to the C function is expected to have type KayaArray. User defined or polymorphic types can also be passed to C; in this case the corresponding argument to the C function is expected to have type KayaValue. These types are defined in the Kaya Value API.

Directives

Foreign calls are implemented simply by placing the call into the C code generated by the Kaya compiler. This code is fed to the C compiler and linked with the necessary libraries. For this reason, the programmer must tell Kaya where to find the declaration and definition of the C function. This is achieved by means of compiler directives, which are of the form %<name> <args>. The most important for foreign function calls are:

  • %include <header> which includes the file header in the generated code
  • %link <libname> which tells the compiler to link with the library libname
  • %imported <object> which tells the compiler to link with the object file object.o

For a simple example of how %include and %imported are used, see the source code of the Binary module. This links with a C file, binary_glue.cc. The Gcrypt module also uses the %link directive to link with libgcrypt.

The Ptr Type

Kaya includes a Ptr primitive type, which maps onto a C void* in a foreign function call. The intended purpose of this is to be able to store data in an arbitrary structure C side, and keep a reference Kaya side. Again, see Binary.k and binary_glue.cc for a simple example of how this works.

Handling errors in foreign code

Error conditions in foreign code should generally result in an appropriate Exception back in Kaya. There are two ways to do this – firstly, the return value of the foreign function may vary on error. You can then check for the different return value easily. If this is not the case, or if it is not practical to test for this (if the return type is <code>Ptr</code>, for example), then you can throw an Exception directly from C. To do this, you need to pass the C function a pointer to the Kaya virtual machine. The following example demonstrates this with the <code>wcsstr</code> C function (the wide character equivalent of <code>strstr</code>), which returns a NULL pointer if the substring is not found.

The C code

#include "VM.h"
#include "KayaAPI.h"

wchar_t* do_wcsstr(void* vmptr,
                   const wchar_t* haystack,
                   const wchar_t* needle) {
    VMState* vm = (VMState*) vmptr; // get the Kaya virtual machine
    wchar_t* result = wcsstr(haystack,needle);
    if (result == NULL) {
// needle not found in the haystack
        vm->kaya_internalError(0);
// this throws the Exception. The int parameter can be used if you need to
// distinguish different types of error
    }
    return result; // otherwise we just return the result
}

The Kaya code

Exception SubstringNotFound;

foreign "stdfuns.o" {
// we need glue functions Kaya side and C side here.
    String do_wcsstr(Ptr vm,String haystack, String needle) = do_wcsstr;
}

String wcsStr(String haystack, String needle) {
    try {
        return do_wcsstr(getVM,haystack,needle);
    } catch(InternalError(err)) {
// we catch the potential C-side error here, and throw a more meaningful
// Exception.
        throw(SubstringNotFound);
    }
}

Instead of re-throwing an Exception Kaya-side, we could instead have used the Maybe data type. Which approach is more sensible depends on the purpose of the function.

// alternative wcsStr
Maybe<String> wcsStr(String haystack, String needle) {
    try {
        return just(do_wcsstr(getVM,haystack,needle));
    } catch(InternalError(err)) {
        return nothing;
    }
}

Generally it is best to rethrow the Exception for ‘unexpected’ errors such as non-existent files or errors in generating C structures (for example, compiled regular expressions) that may be needed later, and to use the Maybe type for ‘expected’ error conditions.

The Kaya Value API

A header file KayaAPI.h is provided to allow a programmer more control over values passed between Kaya and C. Hopefully the header file should be self documenting, at least for the moment!

It is possible through this API to build and query user defined data structures (ie, Unions). The tag of a union is an integer representing which constructor it was built with. These are numbered in order of declaration, starting at 0. The tag of a union cannot be modified after it is built, as each constructor may take a different number of arguments; arguments may be modified, however.

The building of unions is the most error-prone aspect of the foreign function interface, as there is no checking that it is type-safe. The API should therefore be used with care!

Callbacks

KayaCall allows a Kaya function (as stored in a KayaValue) to be called from C. It is a varargs function, and must be told how many arguments to expect. All of its arguments are of type KayaValue.

This function is intended to be used as a means of passing callbacks to C functions. There are currently some issues to consider when doing this:

  • The callback runs in its own virtual machine, which means that exceptions will not be caught. If the function or one of its subroutines might throw an exception, it should make sure it catches the exception.
  • This method cannot be used (without difficulty) to call Kaya libraries from C. It is not expected that there is much demand for this, but if it turns out that there is, it should be easy to implement.

Recent Comments

No comments to show.

Pages