Calling Methods from a C-Module in a Swift Project
This article will show you how to call functions from a C module in Swift, with the module source written in GoLang.
The Module
The module can be written in any language, so long as it’s compiled into binary. To make things interesting, let’s say we have a GoLang program that will be compiled using cgo
into an iOS module.
This GoLang code exports a sayHello()
method, which returns the c
-string hello
:
package mainimport "C"func main() {
// main() won't be called, but it is required for compilation
}//export sayHello
func sayHello() *C.char {
return C.CString("Hello")
}
This file and how to compile it as an iOS module are described in Compile GoLang as a Mobile Library, but what’s important for this tutorial is that the sayHello()
method is exposed through the resulting sayhello.h
.
We can therefore call this method from Swift with a little work.
The Procedure
The basic procedure here is to:
- Create an Xcode Project
- Add the module and header to the project
- Create an Objective-C bridging header
- Call the module’s functions
Let’s see it in action.
1. Create a New Xcode Project
The first step of course is to create a new Xcode project. Although this could be any sort of project, for this tutorial we will be creating a mobile app.
2. Add the Module and Header To The Project
Drag and drop the module and the associated header (.h
) file into the project folder, so that the module is available and the header can be found by the compiler.
You can copy the references or not, whatever works for your project.
3. Create an Objective-C Bridging Header
Create a new Objective-C file, name it <project-name>-Bridging-Header.h
In your new header file, include the module header you will be interacting with:
#include "sayhello.h"
This will allow Swift to talk to a C header, through the Objective-C bridge.
4. Call the module’s functions
Let’s say we create a simple UI featuring a button which triggers the module’s function, and a label which displays the return value.
We can code this in the ContentView.swift
like this:
import SwiftUIstruct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext @State var sayHelloResponse = "" var body: some View {
Button(action: onClick) {
Text("Say Hello")
}
Text("Response from module: \(sayHelloResponse)")
} private func onClick() {
// do something here
}
}
Let’s look at how to call the sayHello()
function in the onClick()
method.
The sayHello()
function returns a c-string. Since Swift doesn’t use c-strings natively, it could cause problems if the method doesn’t return properly or doesn’t terminate. Therefore, we must create a guard
around it and exit the method if there’s a failure:
guard let cstr = sayHello() else { return }
Since Swift uses it’s own String
type, we should convert cstr
to a String
. To do that, we need to get the c-string length using strlen()
, encode as .utf8
, and free the variable for garbage collection when done:
let str = String(
bytesNoCopy: cstr,
length: strlen(cstr),
encoding: .utf8,
freeWhenDone: true
)
Finally, we can store the resulting str
into our @State var sayHelloResponse
variable. But just in case there was a problem converting cstr
to a String
, we need to include a fallback string, in this case "(Failed)"
:
sayHelloResponse = str ?? "(Failed)"
Putting it all together, we get this method:
private func onClick() {
guard let cstr = sayHello() else { return }
let str = String(
bytesNoCopy: cstr,
length: strlen(cstr),
encoding: .utf8,
freeWhenDone: true
)
sayHelloResponse = str ?? "(Failed)"
// do something here
}
And when we run the program and click the “Say Hello” button, the Text
label displays the response from the module:
That’s it! Now you have executed code from an imported module in your iOS project.
Further Reading
There’s another fantastic tutorial related to this one, but using gomobile
instead of cgo
: Calling Go code from Swift on iOS and vice versa with Gomobile