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:
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.