Editor’s Note: This article was written on December 6, 2015, days after Apple open-sourced Swift and made an Ubuntu distribution of the Swift compiler available. All of the techniques used below should be forward compatible, however, there may be easier ways of doing things in the future as the Foundation classes are implemented. Apple has posted a status page that outlines what works and what doesn’t.
Using Glibc Routines with Swift
As I mentioned in Swift on Linux!, the Foundation classes that Objective-C and Swift developers have come to know and love are only partially implemented. And by partially implemented I really mean hardly implemented. Okay, NSError
is there and a few others, but no NSURL
, NSURLSession
, etc.
What is there is the wealth of routines from the GNU C Library, also known as Glibc. You know, the library of rotuines you’d look up with a man page. Functions like popen
and fgets
, getcwd
and qsort
. Swift won’t be displacing Python any time soon if this is all we’re left to work with, but you can do something useful and begin exploring the possibilities of intermixing C with Swift. In this tutorial we’ll do exactly that and write up some Swift code that uses popen
to spawn wget
to make up for the lack of NSURLSession
.
So let’s get stuck in and write some Swift.
Swift cat
Create a file named swiftcat.swift
and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import Glibc guard Process.arguments.count == 2 else { print("Usage: swiftcat FILENAME") exit(-1) } let filename = Process.arguments[1] let BUFSIZE = 1024 var pp = popen("cat " + filename, "r") var buf = [CChar](count:BUFSIZE, repeatedValue:CChar(0)) while fgets(&buf, Int32(BUFSIZE), pp) != nil { print(String.fromCString(buf)!, terminator:"") } exit(0) |
To get access to all of the Glibc routines we use import Glibc
. Easy enough. Swift 2 brought us the guard
construct, so we’ll use that to ensure that we have an argument to our script. Our first exposure to using a Glibc function is exit(-1)
. That’s right, nothing special about calling it, it is just the void exit(int status)
function.
We’re going to cheat a bit and leverage the /bin/cat
command to read the file and write to standard out. To call it though we’ll use popen
which will pipe us a stream of bytes that we can read with fgets
. There is one thing to notice here, and that is that Glibc routines which take const char*
arguments can be given Swift String
s directly. Routines that take char*
, as in the case of fgets
require some finesse.
fgets
does take a char*
, so we cannot pass it a String
, but rather will use a buffer allocated as a [CChar]
(C char) array. The array has a fixed size of 1024 and is initialized with zeroes. Our while
loop calls fgets
with the stream pointer, and non-nil
results contain a buffer from which we can create a Swift String
.
Go ahead and save this to a file called swiftcat.swift
and then run it!
# swift swiftcat.swift Usage: swiftcat FILENAME
Pass it a file to get the equivalent of cat
output!
Mixing in C
You aren’t limited to using Glibc routines with your Swift code. Let’s say we want to use libcurl
to escape some strings and get them ready to be included in a URL. This is easy to do with libcurl.
In a file called escapetext.c
put the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <curl/curl.h> int escapeText(const char* text, char** output) { int rc = -1; CURL* curl = curl_easy_init(); if (curl) { char* escaped = curl_easy_escape(curl, text, strlen(text)); if (escaped) { *output = (char*)malloc(strlen(escaped) + 1); strcpy(*output, escaped); curl_free(escaped); rc = strlen(*output); } } return rc; } #ifdef __TEST__ int main(int argc, char** argv) { if (argc < 2) { printf("Usage: escapetext STRING\n"); exit(-1); } char* text = argv[1]; char* escapedText; int rc = 0; if (escapeText(text, &escapedText)) { printf("Escaped text: %s\n", escapedText); } else { printf("Error\n"); rc = -1; } free(escapedText); exit(rc); } #endif |
Make sure you have libcurl
installed with apt-get install -y libcurl4-gnutls-dev
.
Now, compile the file with:
clang -D__TEST__ -o escapetext escapetext.c -lcurl
We include the -D__TEST__
here to pick up the main
function. In a minute I’ll show you how to take this routine and include it in a Swift application. Run the C application:
# ./escapetext "hey there\!" Escaped text: hey%20there%21
Easy enough. Now, we want to write a Swift application that uses our C routine escapeText
. The first thing to do is compile an escapetext.o
object file without the -D__TEST__
flag set. This will get rid of main()
.
clang -c escapetext.c
Now, create a file called escapetext.h
and put the function prototype in it.
1 |
int escapeText(const char* text, char** output); |
Write a new file called escapeswift.swift
and add the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import Foundation import Glibc guard Process.arguments.count == 2 else { print("Usage: escapeswift STRING") exit(-1) } let string = Process.arguments[1] var output:UnsafeMutablePointer<Int8> = nil let rc = escapeText(string, &output) guard rc > 0 else { print("Error escaping text") exit(-1) } print("Escaped text: \(String.fromCString(output)!)") exit(0) |
Compile this Swift code with:
swiftc -c escapeswift.swift -import-objc-header escapetext.h
Notice that we included -import-objc-header escapetext.h
. Without this header the Swift compiler won’t be able to find the prototype for escapeText
and will subsequently fail with use of unresolved identifier
.
Bringing it all together, we link our escapeswift.o
and escapetext.o
objects together, and pass in the Curl library.
swiftc escapeswift.o escapetext.o -o escapeswift -lcurl
And run it!
# ./escapeswift "how now brown cow" Escaped text: how%20now%20brown%20cow
Translator Application
This is a more complex example, but the principals are the same as those outlined above. We’re going to mix C objects and Swift modules together to write a command line application that translates strings from one language to another.
The REST API we’ll be using to do the actual translation returns results in JSON. Since NSJSONSerialization
isn’t yet available in Foundation on Linux, we’ll use the libjson-c-dev
library, so install it with apt-get install libjson-c-dev
.
jsonparse
Two files make up our JSON-parsing routine, parsejson.c
and its companion header parsejson.h
.
parsejson.c
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <stdio.h> #include <json-c/json.h> const char* translatedText(const char* json) { const char* c = NULL; json_object* jobj = json_tokener_parse(json); json_object* responseData = json_object_object_get(jobj, "responseData"); if (responseData) { json_object* translatedTextObj= json_object_object_get(responseData, "translatedText"); if (translatedTextObj) { c = json_object_get_string(translatedTextObj); } } return c; } |
parsejson.h
1 |
const char* translatedText(const char* json); |
We can easily compile this file with clang -c jsonparse.c
.
Translator module
The workhorse of the translator application will be a Swift module called translator
. To create this module and prepare it for inclusion with the rest of our project, start with the class file translator.swift
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import Glibc import Foundation public class Translator { let BUFSIZE = 1024 public init() { } public func translate(text:String, from:String, to:String, completion:(translation:String?, error:NSError?) -> Void) { let curl = curl_easy_init() guard curl != nil else { completion(translation:nil, error:NSError(domain:"translator", code:1, userInfo:nil)) return } let escapedText = curl_easy_escape(curl, text, Int32(strlen(text))) guard escapedText != nil else { completion(translation:nil, error:NSError(domain:"translator", code:2, userInfo:nil)) return } let langPair = from + "%7c" + to let wgetCommand = "wget -qO- http://api.mymemory.translated.net/get\\?q\\=" + String.fromCString(escapedText)! + "\\&langpair\\=" + langPair let pp = popen(wgetCommand, "r") var buf = [CChar](count:BUFSIZE, repeatedValue:CChar(0)) var response:String = "" while fgets(&buf, Int32(BUFSIZE), pp) != nil { response = response + String.fromCString(buf)! } let translated = translatedText(response) completion(translation:String.fromCString(translated)!, error:nil) } } |
Take a moment to read through the code. We’re including direct calls to the Curl library here, as well as popen
and fgets
, and our translatedText
routine that is compiled into an object file created by clang
.
In addition, create a bridgingHeader.h
with the contents:
1 2 |
#include <curl/curl.h> #include "jsonparse.h" |
There are two steps to getting this ready to use in our application:
- Create a shared library with the translator routine
- Create a
swiftmodule
that describes the interface
I will confess, I didn’t understand this until I read on Stackoverflow:
The
.swiftmodule
describes the Swift module’s interface but it does not contain the module’s implementation. A library or set of object files is still required to link your application against.
First, compile the code into a .o
and create a shared library:
swiftc -emit-library translator.swift -module-name translator -import-objc-header bridgingHeader.h clang -shared -o libtranslator.so translator.o
Now, create the module:
swiftc -emit-module -module-name translator translator.swift -import-objc-header bridgingHeader.h
This leaves us with three files: libtranslator.so
, translator.swiftmodule
, and translator.swiftdoc
.
Main Routine
Our main file, main.swift
looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import translator import Foundation import Glibc guard Process.arguments.count == 6 && Process.arguments[2] == "from" && Process.arguments[4] == "to" else { print("Usage: translate STRING from LANG to LANG") exit(-1) } let string = Process.arguments[1] let fromLang = Process.arguments[3] let toLang = Process.arguments[5] let translator = Translator() translator.translate(string, from:fromLang, to:toLang) { (translation:String?, error:NSError?) -> Void in guard error == nil && translation != nil else { print("Error: No translation available") exit(-1) } if let translatedText = translation { print("Translation: " + translatedText) exit(0) } } |
Again, we’ve made use of Foundation and Glibc, but we’re also using import translator
. You must have a translator.swiftmodule
in your module search path, which we add with -I.
:
swiftc -I. -c main.swift -import-objc-header bridgingHeader.h
Let’s link everything together:
swiftc -o translate.exe jsonparse.o main.o -L. -ltranslator -lcurl -ljson-c -lswiftGlibc -lFoundation
The resulting binary is translate.exe
because we intend to wrap a helper script around it to set the LD_LIBRARY_PATH
to find the libtranslator.so
shared library. Without the helper script (or using ldconfig
to update the search path), you need to invoke the excecutable like this:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./translate.exe "Hello world\!" from en to es Translation: ¡Hola, mundo!
Let’s try Irish:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./translate.exe "Hello world\!" from en to ga Translation: Dia duit
Makefile
It’s not clear how “interpreter” friendly Swift will become. Yes, one can create a single monolithic Swift script right now and run it with swift
. In fact we did that above. Using bits of code from other Swift files though, without specifying everything on the command line, remains, well, impossible. Maybe I’m wrong and just haven’t figured out the magic incantation to have Swift greedily open up files searching for code to run.
At any rate, our translator application above needs a little help to build. There is a new Swift builder tool, but I found make
could get the job done with some appropriate rules:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
APP=translate.exe all: $(APP) %.o:%.c clang -c $< %.o:%.swift swiftc -I. -c $< -import-objc-header bridgingHeader.h $(APP): libtranslator.so translator.swiftmodule jsonparse.o main.o swiftc -o $(APP) jsonparse.o main.o -L. -ltranslator -lcurl -ljson-c -lswiftGlibc -lFoundation libtranslator.so: translator.o swiftc -emit-library translator.swift -module-name translator -import-objc-header bridgingHeader.h clang -shared -o libtranslator.so translator.o translator.swiftmodule: libtranslator.so swiftc -emit-module -module-name translator translator.swift -import-objc-header bridgingHeader.h clean: rm -rf *.o *.swiftmodule *.swiftdoc *.so $(APP) |
Getting the Code
You can get all of the code above from Github.
git clone https://github.com/iachievedit/moreswift
The swiftcat
code is meant to be run with the swift
command, where as escapetext
has a simple build.sh
script, and translate
has a full-on Makefile
.
If you’ve enjoyed this tutorial please follow us Twitter at @iachievedit! There will be more to come as Swift on Linux matures.
Thanks !
I have been searching since the Ubuntu release for how to use standard Linux libraries. This gives me hope.
Cool resource you have provided here!
Found small typos related to file naming
… in bridgingHeader.h:
#include
#include "jsonparse.h"
should be
#include "parsejson.h"
and a related one
Let’s link everything together:
swiftc -o translate.exe jsonparse.o main.o -L. -ltranslator -lcurl -ljson-c -lswiftGlibc -lFoundation
should be
swiftc -o translate.exe parsejson.o main.o -L. -ltranslator -lcurl -ljson-c -lswiftGlibc -lFoundation
Of course, alternatively should have it have different filenames when same. It would be helpful if they were all consistent.
Also, I was having strange escaping the bang behavior on my Ubuntu boxes (it was appearing as a backslash + a bang), but I found this to work (note the single quotes, and no backslash):
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./translate.exe 'Hello world!' from en to es