Import C code and use it right inside your projects
If you’ve ever found a fantastic library that you’d love to use in your code, only to find out that it’s written in C, I have some good news: with 10 minutes of work you can get a simple wrapper that lets you use it from Swift, and with a few hours of work you can get a great wrapper that lets you use more natural Swift code.
To demonstrate this I’m going to walk you through what it took to create one of my C library wrappers, SwiftGD. I created this to wrap the GD image framework so that you can create images and draw shapes in places where Core Graphics isn’t available, and it’s used in my book Server-Side Swift.
You can see the complete SwiftGD project at its GitHub repo, but here we’re going to do a much simpler job just to get you started.
Note: You need to have the GD library installed on your computer in order to follow this tutorial. If you're using macOS, install Homebrew then run the command brew install gd. If you're using Linux, run apt-get libgd-dev as root.
SAVE 50% To celebrate Black Friday, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Believe it or not, Swift is able to do most of the work in order to create basic wrappers around C libraries – you simply point it some header files and it will bridge them to Swift. That doesn’t mean your finished Swift framework is nice, though, because you still need to do memory management yourself.
As a result, common practice is to split your Swift wrapper into two parts: one that wraps the Swift framework using Swift’s automatic functionality, and one that wraps the other in much nicer code. We’re wrapping a library called GD, so the convention is to create a simple wrapper called Cgd and a more Swifty wrapper called SwiftGD.
So, to get started we need to run a few terminal commands, so please launch the Terminal app. To start with change to your Desktop directory, create a new directory called “Cgd”, then change it into that:
cd Desktop
mkdir Cgd
cd Cgd
This will house the simple Swift wrapper.
The second step is to have Swift Package Manager (SPM) create an empty package for us. It has a built-in “system-module” type designed exactly for this purpose, so please run this command now:
swift package init --type system-module
That will generate four files for you:
Believe it or not, we’re already half way to wrapping GD in Swift!
The third step is to adjust Package.swift just a little so that users who haven’t installed GD will get helpful error messages. This isn’t required, but it makes the end-user experience a little more pleasant.
Your Package.swift file should look like this right now:
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Cgd",
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
)
We’re going to change that to add two things:
pkgConfig
line that describe what GD is called in pkg-config. This is a system tool that can query how various libraries are installed and configured, and is hugely popular on Linux.providers
array that describes what Homebrew and APT packages should be run in order to get GD installed. We did this by hand above, but some users might forget.Modify your Package.swift file to this:
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "Cgd",
pkgConfig: "gdlib",
providers: [
.brew(["gd"]),
.apt(["libgd-dev"])
]
)
The final step is to tell Swift where it can find GD’s header files. It can then scan those files to see how GD works, and provide an automatic Swift wrapper we can use immediately.
This is all controlled inside the file module.modulemap, which should look like this:
module Cgd [system] {
header "/usr/include/Cgd.h"
link "Cgd"
export *
}
That declares a system module called Cgd, tells it to read the C header file "/usr/include/Cgd.h”, link against the “Cgd” library, and export all the functionality it finds.
That won’t work, of course: Cgd is the name of our wrapper rather than the C library itself. However, another problem it has is that macOS and Linux store their header files in different places – we can’t rely on "/usr/include/gd.h" existing, because on macOS it will actually be in "/usr/local/include/Cgd.h”.
To resolve this we’re going to modify module.modulemap so that it defines two system modules: one called Cgdlinux that looks in “/usr/include/gd.h”, and one called Cgdmac that looks in “/usr/local/include/gd.h”. They are otherwise the same: both should link against the “gd” library and export all their functionality.
Modify module.modulemap to this:
module Cgdlinux [system] {
header "/usr/include/gd.h"
link "gd"
export *
}
module Cgdmac [system] {
header "/usr/local/include/gd.h"
link "gd"
export *
}
And that’s it - we’ve created a simple Swift wrapper around the GD library, without writing any actual Swift code.
Before we use that in some real code, we need to make one non-code change. You see, SPM is built around Git repositories: it uses Git to copy dependencies locally, then check it has specific versions.
So, in order to use Cgd in real code we need to make it a local Git repository then give it a version tag. Run these commands:
git init
git add .
git commit -m "Initial commit."
git tag 0.0.1
That creates the repository locally, adds all source code to it, saves that code to the repository, then tags it as a v0.0.1. A Git tag is a simple marker in the code, but it allows SPM to make sure it has the right code.
That’s Cgd complete, so let’s go ahead and try it now. First, go to a new directory and create a new executable project:
cd ..
mkdir GDTest
cd GDTest
swift package init --type executable
That will generate a new skeleton project, along with its own Package.swift file. In order to use our new Cgd library we need to modify that to read the local directory containing Cgd, so open Package.swift now. It should contain the following:
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "GDTest",
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "GDTest",
dependencies: []),
]
)
We’re going to modify that to include a dependency on the Cgd library we just made. This is as simple as replacing the // .package
comment with this:
.package(url: "../Cgd", from: "0.0.1"),
That tells SPM to copy the dependency from the Cgd directory, then look for the 0.0.1 tag we made.
The last step is to write some Swift code to take our library for a test drive. Yes, Swift code: Swift has done the hard work of reading the C header file we specified, and has automatically exposed all its functionality as Swift function calls. That includes mapping things like C strings to Swift strings, so as long as you don’t mind doing memory management you could leave it here if you wanted.
Open the file Sources/GDTest/main.swift and give it this content:
// import either Cgdlinux or Cgdmac depending on our platform, along with whatever framework gives us standard C functions
#if canImport(Darwin)
import Darwin
import Cgdmac
#else
import Glibc
import Cgdlinux
#endif
// create an 800x600 image
let image = gdImageCreateTrueColor(800, 600)
// create two colors: bright red and bright blue – GD uses 0-255 for colors, and 0-127 for alpha,
// but the alpha is inverted so 0 means "opaque" and 127 means "transparent".
let red = gdImageColorAllocateAlpha(image, 255, 0, 0, 0)
let blue = gdImageColorAllocateAlpha(image, 0, 0, 255, 0)
// fill the image red starting from the top-left corner, then draw a blue ellipse on top; coordinates are X, Y, width, height
gdImageFill(image, 0, 0, red)
gdImageFilledEllipse(image, 400, 300, 200, 100, blue)
// open a file for Writing (w) in Binary mode (b)
let outputFile = fopen("output.png", "wb")
// close that file once our code finishes
defer { fclose(outputFile) }
// write out the image
gdImagePng(image, outputFile)
Apart from none of the functions having parameter labels, that reads as pretty good Swift code even though we’ve barely done any work.
To try it out, run swift run from the terminal. Once you code has been compiled and run, you should see output.png waiting for you. It’s not an especially attractive picture, but to be fair this tutorial is about wrapping C APIs rather than drawing images!
Now that we have Cgd in place we can now create a second Swift wrapper: a wrapper around the wrapper, providing a more natural Swift implementation.
Specifically, this means:
Int
type, because these kinds of conversions are annoying.Color
struct that lets users describe colors in a more familiar way.Image
class that can be created at a specific size, filled with various colors, then drawn on.So, go back to your desktop and create a new library project like this:
cd ..
mkdir SwiftGD
cd SwiftGD
swift package init
We didn’t provide a --type parameter to swift package init, so it will generate a library project by default. Library projects are different from executable projects because they lack a main.swift file – you’ll see Sources/SwiftGD/SwiftGD.swift instead.
Before we can start writing code there, we first need to edit Package.swift to import Cgd, just like we did before. So, please open that now and replace the // .package
comment with this:
.package(url: "../Cgd", from: "0.0.1"),
In my real SwiftGD project there are multiple source code files to handle different aspects of the project, but in this simple example it’s easiest just to write code directly into SwiftGD.swift. So, please open that file for editing now – something like Xcode is fine.
Even though I’ve tried to strip it back to the bare basics, writing our wrapper takes several few steps. We’re going to:
import
logic at the top, so whoever uses SwiftGD no longer has to worry about Cgdlinux and Cgdmac.Color
struct that stores RGBA values as doubles. GD prefers integers between 0 and 255 (or 1 to 127 for alpha), but Apple developers are more used to values between 0 and 1.Image
class that can be created at a specific size and destroyed when the image is no longer needed.write()
method that will save output.png just like we had before.We’re going to tackle them individually. First, we need to get the imports straight, which is the same code we used earlier in GDTest except this time with a bonus import Foundation
so we can use CGPoint
and CGSize
.
Replacing the existing contents of SwiftGD.swift with this:
#if canImport(Darwin)
import Darwin
import Cgdmac
#else
import Glibc
import Cgdlinux
#endif
import Foundation
The second step is to define a simple Color
struct that makes it easier for users to work with colors. This will just store four doubles called red
, green
, blue
, and alpha
, but due to the way Swift works we must also create a public initializer so that it can be created by users of our API.
Add this code to SwiftGD.swift:
public struct Color {
public var red: Double
public var green: Double
public var blue: Double
public var alpha: Double
public init(red: Double, green: Double, blue: Double, alpha: Double) {
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
}
}
The third step is to create an Image
class that can create a GD image at any size and destroy it when it’s no longer needed. You might have wondered why Image
is a class while Color
is a struct, but the reason is simple: classes have deinitializers that get called when the class is being destroyed, which is our opportunity to free GD’s internal memory allocated for our image data.
Previously we created the GD image directly by using this code:
let image = gdImageCreateTrueColor(800, 600)
We never actually had to store the image anywhere, and neither did we have to free that RAM – it was automatically destroyed when the program ended. Here, though, we’re taking a more Swifty approach: we don’t want users of our API to have to worry about allocating and freeing memory, so we’ll use class deinitializers to free up their memory automatically.
Add this class to SwiftGD.swift now:
public class Image {
// storage for our internal GD memory
private var internalImage: gdImagePtr
public init(width: Int, height: Int) {
// convert the integer types, then stash away GD's resulting image data
internalImage = gdImageCreateTrueColor(Int32(width), Int32(height))
}
deinit {
// when this object is being destroyed, free its GD memory automatically
gdImageDestroy(internalImage)
}
}
The fourth step is to write methods that can fill the background and draw an ellipse. This took only two lines of code previously:
gdImageFill(image, 0, 0, red)
gdImageFilledEllipse(image, 400, 300, 200, 100, blue)
However, this time we need to do more work to make it more natural for Swift users. For the fill()
method we need to convert our Color
struct into red, green, blue values ranging from 0 to 255, plus 0 to 127 for alpha – although as I said above, the alpha value is inverted from what we're used to so we'll need to flip it. We’ll then allocate the color and fill it from the top-left corner.
Add this method to the Image
class:
public func fill(_ color: Color) {
let red = Int32(color.red * 255.0)
let green = Int32(color.green * 255.0)
let blue = Int32(color.blue * 255.0)
// flip the alpha so that 1.0 becomes "fully opaque"
let alpha = 127 - Int32(color.alpha * 127.0)
let internalColor = gdImageColorAllocateAlpha(internalImage, red, green, blue, alpha)
defer { gdImageColorDeallocate(internalImage, internalColor) }
gdImageFill(internalImage, 0, 0, internalColor)
}
Writing the fillEllipse()
method is similar, except now we also need to convert values from CGPoint
and CGSize
:
public func fillEllipse(center: CGPoint, size: CGSize, color: Color) {
let red = Int32(color.red * 255.0)
let green = Int32(color.green * 255.0)
let blue = Int32(color.blue * 255.0)
let alpha = 127 - Int32(color.alpha * 127.0)
let internalColor = gdImageColorAllocateAlpha(internalImage, red, green, blue, alpha)
defer { gdImageColorDeallocate(internalImage, internalColor) }
gdImageFilledEllipse(internalImage, Int32(center.x), Int32(center.y), Int32(size.width), Int32(size.height), internalColor)
}
The final step is to create a write()
method that will write our image to output.png. You’re welcome to expand this to support any file URL
and different file formats, but here we’re just going to mimic the behavior from before:
public func write() {
let outputFile = fopen("output.png", "wb")
defer { fclose(outputFile) }
gdImagePng(internalImage, outputFile)
}
That completes our improved wrapper, but before we can test it we need to make it a tagged Git repository like this:
git init
git add .
git commit -m "Initial commit"
git tag 0.0.1
At this point we’ve created the basic Cgd wrapper around GD, then created a wrapper around Cgd called SwiftGD. SwiftGD took quite a bit of code, but the end result is a much nicer user experience for our API.
As a reminder, here’s the code we put inside GDTest:
#if canImport(Darwin)
import Darwin
import Cgdmac
#else
import Glibc
import Cgdlinux
#endif
let image = gdImageCreateTrueColor(800, 600)
let red = gdImageColorAllocateAlpha(image, 255, 0, 0, 0)
let blue = gdImageColorAllocateAlpha(image, 0, 0, 255, 0)
gdImageFill(image, 0, 0, red)
gdImageFilledEllipse(image, 400, 300, 200, 100, blue)
let outputFile = fopen("output.png", "wb")
defer { fclose(outputFile) }
gdImagePng(image, outputFile)
Let’s rewrite that using our new SwiftGD wrapper. First, open Package.swift inside GDTest and change it to this:
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "GDTest",
dependencies: [
.package(url: "../SwiftGD", from: "0.0.1"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "GDTest",
dependencies: ["SwiftGD"]),
]
)
That changes both the .package
line and the dependencies
line near the end.
With that, we can rewrite GDTest.swift to use the new Image
and Color
types we just created:
import Foundation
import SwiftGD
let image = Image(width: 800, height: 600)
let cyan = Color(red: 0, green: 1, blue: 1, alpha: 1)
let magenta = Color(red: 1, green: 0, blue: 1, alpha: 1)
image.fill(cyan)
image.fillEllipse(center: CGPoint(x: 400, y: 300), size: CGSize(width: 200, height: 100), color: magenta)
image.write()
I think you’ll agree that’s much nicer!
I hope this has given you a simple and applicable introduction to wrapping C libraries in Swift. As you saw, Swift can do a lot of the work to create a basic wrapper for us, but if you want a Swifty wrapper – one that doesn’t leave users worry about memory management or rely on them using Int32
everywhere – then you should wrap the wrapper in your own code. It takes a little more work, but the end result is definitely worth it!
If you’d like to see what else GD can do, take a look at my SwiftGD project on GitHub. It follows the same basic set up as the code here, except it adds more functionality – different file formats, rendering text, image resizing, and more.
SAVE 50% To celebrate Black Friday, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.