Pragmatism in the real world

Function pointers in my Swift CCurl library

By default, libcurl writes the data it receives to stdout. This is less than useful when writing an application, as we want to store the received data internally and process it.

This is done using the libcurl settings CURLOPT_WRITEFUNCTION which takes a function pointer where you can process the received data and CURLOPT_WRITEDATA which lets you set a pointer to something that can store the received data. You get access to this pointer within your write function.

To do this with my CCurl wrapper around libcurl, I needed to create two new curl_easy_setopt shim functions called curl_easy_setopt_func and curl_easy_setopt_pointer in sots_curl.c. These functions aren’t hard to write as they are simply setting the correct type on the third parameter so that Swift knows how to handle them.

On the Swift side of things, we use a class to hold the bytes we receive so that we can easily pass it to lib curl and not have to worry about managing the memory of data we’re adding:

public class Received {
    var data = String()
}

We then tell curl about it:

let received = Received()
let pReceived = UnsafeMutablePointer(Unmanaged.passUnretained(received).toOpaque())

curl_easy_setopt_pointer(handle, CURLOPT_WRITEDATA, pReceived)

We create an instance of Received and then create an UnsafeMutablePointer to it and set that as our curl WRITEDATA property.

I ended up using a class as I couldn’t work out how to pass a String directly without segmentation faults when executing the app! I think this is because I would need to manage the memory of the String myself, but if I use a class, then I’m only passing the pointer to the class around and it can manage the memory of the String property within it.

The callback now looks like this:

let writeFunc: curl_func = { (buffer, size, num, p) -> Int in
    let received = Unmanaged<Received>.fromOpaque(COpaquePointer(p)).takeUnretainedValue()
    let bytes = UnsafeMutablePointer<UInt8>(buffer)
    let count = size*num
    for idx in 0..<count {
        received.data.append(UnicodeScalar(bytes[idx]))
    }
    return count
}
curl_easy_setopt_func(handle, CURLOPT_WRITEFUNCTION, writeFunc)

We convert p back into a Received object and then can append to the data string property. Usefully UnicodeScalar converts an integer into the relevant character, though we probably need some error handling here.

Now, I’m at point where I can wrap all this into a Swift class and talk to web services.