Wednesday, 5 September 2018

Wrapping C Function Callbacks to Swift (Part 1)

While Swift is a fantastic language, sometimes it can make more sense to reuse an existing C library rather than re-write new code from the ground up with Swift. A lot of open source libraries are written in C because it is one of the most portable languages ever created. Fortunately, Swift has excellent support for calling C code. However, the direct mapping of a libraries C API may not be the most intuitive and so a wrapper is often used to provide a more swifty API on top of the underlying C implementation.
Often C functions have callbacks, these usually take the form of function pointers that are passed by the caller to a C utility function that can invoke the callback function through a pointer. This is a precursor to the higher-level functions and closures within Swift, the ability to pass functions to functions.
I’ll cover this topic in 3 parts, starting with the easiest and going to the most complex.
  1. Wrapping callbacks with a void pointer context parameter
  2. Wrapping Synchronous callbacks without a void pointer
  3. Wrapping Asynchronous callbacks without a void pointer

Fortunately, the easiest scenario is also the most common. This is when the C function in addition to taking a function pointer, also allows the caller to pass a context parameter that will be passed to the callback function. The exact name of the context parameter may vary between different C libraries.
Ever since the introduction of object-oriented languages like C++ and Objective-C, the challenge of mapping older C procedural code into an object model has existed. Ideally, we’d like to map these flat function calls into method invocations on an object instance. However, a method invocation requires at least two pieces of information (not including any other parameters), a reference to the object instance and a reference to the method to be invoked. Passing this instance reference from the caller and having access to it in the callback is the key to wrapping the code in an object-oriented API.
The C language obviously does not have a concept of a swift object. Therefore, a reference to the object must be reduced to a void pointer, these are pointers in C that specify no type information about the type the pointer references. These are mapped to the UnsafeMutableRawPointer type in Swift. We can reduce a reference to an object to a pointer via the Unmanaged type that has methods to convert Swift references to and from raw pointers.
and can be converted back via: -

In Swift you can supply a closure to a C function that takes a function pointer. The type of this closure will be marked with the @convention(c) attribute. However, there are some big limitations with the closures that can be used. That closure cannot capture any context and therefore it can only work with the parameters passed to it. Therefore, you cannot use this as a mechanism to pass a reference to an object instance. If you attempt to you will receive an “error: a C function pointer cannot be formed from a closure that captures context”.

For a real example I will use libxml2, as the example since I had previous written a swift wrapper around the SAX based HTML parser within this C library (see Github for this project). The API of this library takes a struct of C function pointers that are invoked based on the SAX events during the parsing process. The following represents an example of assigning one of the callbacks.
We create a parsing context before invoking the parsing function and and pass the UnsafeMutableRawPointer version of this context to the parsing function.

No comments:

Post a Comment