Elektra  0.9.3
Bindings for the High-level API

This document describes how and when to write a language binding for the high-level API.

Writing bindings for high-level API is different from writing bindings for other parts of Elektra. This is mainly because the high-level API has different goals.

The goals of any high-level API (or binding to the C high-level API) for Elektra should be:

Based on the goals above, you should decide, whether it is possible to write a binding for the C high-level API while preserving the goals.

If you decide to write a binding, proceed with this tutorial.

If not, designing an API to meet our goals is up to you. While designing the API you should keep in mind that you can always use code-generator (kdb gen) templates like the C API does.

Since the C high-level API consists of a shared library API and a code-generated part, your binding will also have these two parts.

Writing the binding for the highlevel library works the same way as writing a language binding for any part of Elektra. The only additional challenge is that the binding should still meet the same goals and (as far as possible) fulfill the same guarantees as the C API.

Some languages like to split bindings into two parts. One version that maps the C API one-to-one and another part that builds on the first one. The second part then uses the languages additional features. Since our API isn't intended to be used directly, but through generated code, it might not be necessary to write the second part. It may be sufficient to write (or generate) a one-to-one mirror of the C API and use that in the code-generator template.

The generated code should still be understandable. So if writing a more idiomatic API on top of the direct mapping, significantly simplifies the generated code (and maybe also the template), you should write such an API.

Your programming language of choice must provide a way to call into C code (like cgo).

In general we prefer (in this order):

  1. Automatically and statically generated bindings (like swig).
  2. Manually written bindings that directly call to library without any statically build part (like JNI and GI). They are a bit slower but can directly access libelektra.so.
  3. Manually written bindings that are built statically.

If you want to manually write a binding make sure you have a good understanding of the possible limitations the interop layer can have (e.g. variadic functions, freeing of resources, ...).

What you will also need is to set up the compiler + linker flags. For this we recommend pkg-config, because Elektra already provides .pc (and cmake) files.

For garbage collected (GC) languages freeing memory by hand is not something you usually do and since the GC has no knowledge of memory allocated in C we have two options:

If you decide on mapping the functionality of kdb.h 1:1 it is pretty straightforward - whereas if you want to adapt or enhance some API's to leverage language features like iterators or operator overloading feel free to do so. The less “alien” the binding feels to its users the better.

Remember that Elektra has internal iterators (for metadata+keysets) but in general we prefer external iterators by either copying the KeySet per iterator or using ksAtCursor.

Some languages for example cannot call variadic functions because in C the amount of parameters has to be known at compile-time. In Go for example this is not the case since it supports variable length arguments at runtime with the ... operator.

This is unfortunate because the low-level bindings rely heavily on variadic functions. It is possible to work around this problem by either

  1. Writing helper functions in C that call these variadic functions with a fixed amount of parameters or
  2. Imitating the behavior of a function e.g. keyNew() by calling multiple functions: keyNew() and keySetMeta() for every metakey/value that was passed.

How to create a template for kdb gen is detailed in this tutorial.

The created template should support the same input keysets (and parent keys) as the highlevel template. If and how exactly you implement the advanced features (structs, unions, ...) is up to you. Note: enums should always be supported (if your language has them) as they are one of the type plugins types.

For example, in C++ the generated code could consist of nested structs with overloaded operators. In that case the structs features doesn't really make sense, since everything is already structs. Of course you could reuse some of the gen/* metadata to allow some cross-compatibility.