Pragmatism in the real world

Wrapping variadic functions for use in Swift

As I noted in my post about getting libcurl working with Swift, curl_easy_setopt() is a variadic function which means that Swift will not import it. If you try to use it you get this error:

error: 'curl_easy_setopt' is unavailable: Variadic function is unavailable
curl_easy_setopt(handle, CURLOPT_VERBOSE, true)
^~~~~~~~~~~~~~~~
CCurl.curl_easy_setopt:2:13: note: 'curl_easy_setopt' has been explicitly marked unavailable here
public func curl_easy_setopt(curl: UnsafeMutablePointer, _ option: CURLoption, _ varargs: Any...) -> CURLcode
            ^

It’s mildly annoying as curl_easy_setopt only ever takes three parameters! I assume that it’s marked as a variable argument function because the last parameter can be any type?

To solve this, I wrote a simple C library that exposes new functions that are not variadic and take a known type as their last parameter as I’m not sure if I can write a function in C that takes an unknown type and use it in Swift in a sane way.

Create a static C library

So, to start simply, we’ll create a library that implements curl_easy_setopt_string and curl_easy_setopt_bool.

The C for this is easy enough. Start with a mycurl.h file:

#ifndef __MYCURL_H
#define __MYCURL_H

#include 
#include "mycurl.h"

CURLcode curl_easy_setopt_string(CURL *curl, CURLoption option, const char *param);
CURLcode curl_easy_setopt_bool(CURL *curl, CURLoption option, bool param);

#endif

The C file, mycurl.c is equally simple:

#include 
#include 

CURLcode curl_easy_setopt_string(CURL *curl, CURLoption option, const char *param) {
    return curl_easy_setopt(curl, option, param);
}

CURLcode curl_easy_setopt_bool(CURL *curl, CURLoption option, bool param) {
    return curl_easy_setopt(curl, option, param);
}

(I don’t think I’ll get any awards for this code!)

To build the C file into an object file we use:

$ clang -c mycurl.c -o mycurl.o

and then we turn it into a static library using

$ ar res mycurl.o libmycurl.a

One limitation of the Swift Package Manager (as far as I can tell) is that it will only link libraries that are LD_LIBARARY_PATH. Hence, we need to copy libmycurl.a to /usr/local/lib:

$ sudo cp libmycurl.a /usr/local/lib/

Integrate our library with Swift

To integrate our library with Swift, we can simply update the CCurl package that we created earlier.

The module.modulemap file we have so far is:

module CCurl [system] {
    header "/usr/include/curl/curl.h"
    link "curl"
    export *
}

and we update it with the new information, so that it now looks like:

module CCurl [system] {
    header "/usr/include/curl/curl.h"
    header "mycurl.h"
    link "curl"
    link "mycurl"
    export *
}

Note that it seems happy that the header is a relative path, but that the link to mycurl (it doesn’t need the prepended “lib“) cannot be relative. If there’s a way to make it relative, please let me know!

Having updated our CCurl module, we tag it with version 0.0.2 and can then use it in our app and actually load a webpage!

Update the app’s main.swift file so that it is more complete:

import CCurl

let handle = curl_easy_init()

curl_easy_setopt_string(handle, CURLOPT_URL, "http://www.example.com")
curl_easy_setopt_bool(handle, CURLOPT_VERBOSE, true)

let ret = curl_easy_perform(handle)
let error = curl_easy_strerror(ret)

print("error = \(error)")
print("ret = \(ret)")

curl_easy_cleanup(handle);

A quick swift build later and you can run it:

$ .build/debug/app

For me, the output looks like this:

Swift curl1

Conclusion

I’ve wrapped up all that I’ve learnt so far into a repository on GitHub called CCurl, so all you actually need to do is add it as a dependency in your Package.swift:

.Package(url: "https://github.com/SwiftOnTheServer/CCurl.git", majorVersion: 1)

Next on my list is working out how to do a callback so that I can collect the website’s HTML rather than it echoing to stdout!

2 thoughts on “Wrapping variadic functions for use in Swift

Comments are closed.