UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to wrap a C library in Swift

Import C code and use it right inside your projects

Paul Hudson       @twostraws

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.

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Creating a basic wrapper

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:

  • Package.swift describes your Cgd module: what it’s called, what it relies on, and how it’s configured.
  • README.md is a simple Markdown file describing your project. If you put it on GitHub it will be shown when folks visit your repo.
  • .gitignore will stop several unimportant files being added to source control along with your code.
  • module.modulemap is where the magic happens – it tells Swift where to find GD’s C header files so it can wrap them.

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:

  1. A 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.
  2. A 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.

Testing the wrapper

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!

Wrapping the wrapper

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:

  • Creating a new library called SwiftGD that imports Cgd.
  • Wrapping integers in Swift’s Int type, because these kinds of conversions are annoying.
  • Hiding all the Cgdlinux and Cgdmac mess; users don’t want to care about that.
  • Creating a Color struct that lets users describe colors in a more familiar way.
  • Creating an 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:

  1. Put all the import logic at the top, so whoever uses SwiftGD no longer has to worry about Cgdlinux and Cgdmac.
  2. Define a 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.
  3. Create an Image class that can be created at a specific size and destroyed when the image is no longer needed.
  4. Add methods to fill the background and draw filled ellipses.
  5. Finally, add a 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

Testing the wrapper wrapper(!)

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!

Where next?

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.

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.4/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.