📖

How to use a Swift library in C

In this tutorial, we're going to build a C app by importing a Swift library and talk a bit about the Swift / C Interoperability in general.

Swift C

How to build a C compatible Swift library?

In order to create a Swift library that's going to work with C, we have to play around with unsafe memory pointers to create a C compatible interface. Fortunately I was able to find a nice example, which served me as a good starting point, on the Swift forums created by Cory Benfield, so that's what we're going to use in this case. Thanks you. 🙏

final class MyType {
    var count: Int = 69
}

@_cdecl("mytype_create")
public func mytype_create() -> OpaquePointer {
    let type = MyType()
    let retained = Unmanaged.passRetained(type).toOpaque()
    return OpaquePointer(retained)
}

@_cdecl("mytype_get_count")
public func mytype_get_count(_ type: OpaquePointer) -> CInt {
    let type = Unmanaged<MyType>.fromOpaque(UnsafeRawPointer(type)).takeUnretainedValue()
    return CInt(type.count)
}

@_cdecl("mytype_destroy")
public func mytype_destroy(_ type: OpaquePointer) {
    _ = Unmanaged<MyType>.fromOpaque(UnsafeRawPointer(type)).takeRetainedValue()
}

The good news is that we don't necessary have to create a separate header file for our interfaces, but the Swift compiler can generate it for us if we provide the -emit-objc-header flag.

I have an article about the swiftc command for beginners and I also wrote some things about the Swift compiler, where I talk about the available flags. This time we're going to use the -module-name option to specify our module name, we're going to generate the required files using the -emit-dependencies flag, parse the source files as a library (-parse-as-library), since we'd like to generate a Swift library provide the necessary target and version information and emit a header file.

# macOS
swiftc \
        -module-name mytype \
        -emit-dependencies \
        -parse-as-library \
        -c mytype.swift \
        -target arm64-apple-macosx12.0 \
        -swift-version 5 \
        -emit-objc-header \
        -emit-objc-header-path mytype.h

# Linux (without the target option)
swiftc \
    -module-name mytype \
    -emit-dependencies \
    -parse-as-library \
    -c mytype.swift \
    -swift-version 5 \
    -emit-objc-header \
    -emit-objc-header-path mytype.h

This should generate a mytype.h and a mytype.o file plus some additional Swift module related output files. We're going to use these files to build our final executable, but there are a few more additional things I'd like to mention.

Under Linux the header file won't work. It contains a line #include Foundation/Foundation.h and of course there is no such header file for Linux. It is possible to install the GNUstep package (e.g. via yum: sudo yum install gnustep-base gnustep-base-devel gcc-objc, but for me the clang command still complained about the location of the objc.h file. Anyway, I just removed the include Foundation statement from the header file and I was good to go. 😅

The second thing I'd like to mention is that if you want to export a class for Swift, that's going to be a bit harder, because classes won't be included in the generated header file. You have two options in this case. The first one is to turn them into Objective-C classes, but this will lead to problems when using Linux, anyway, this is how you can do it:

import Foundation

@objc public final class MyType: NSObject {
    public var count: Int = 69
}

I prefer the second option, when you don't change the Swift file, but you create a separate header file and define your object type as a struct with a custom type (mytype_struct.h).

typedef struct mytype mytype_t;

We're going to need this type (with the corresponding header file), because the mytype_create function returns a pointer that we can use to call the other mytype_get_count method. 🤔

Compiling C sources using Swift libraries So how do we use these exposed Swift objects in C? In the C programming language you just have to import the headers and then voilá you can use everything defined in those headers.

#include <stdio.h>
#include "mytype.h"

int main() {
    mytype_t *item = mytype_create();

    int i = mytype_get_count(item);
 
    printf("Hello, World! %d\n", i);

    return 0;
}

We can use clang to compile the main.c file into an object file using the necessary header files.

# macOS
clang -x objective-c -include mytype.h -include mytype_struct.h -c main.c

# Linux
clang -include mytype.h -include mytype_struct.h -c main.c

This command will build a main.o file, which we can use to create the final executable. 💪

Linking the final executable

This was the hardest part to figure out, but I was able to link the two object files together after a few hours of struggling with the ld command and other framework tools I decided to give it up and let swiftc take care of the job, since it can build and link both C and Swift-based executables.

We're going to need a list of the object files that we're going to link together.

ls *.o > LinkFileList

Then we can call swiftc to do the job for us. I suppose it'll invoke the ld command under the hood, but I'm not a linker expert, so if you know more about this, feel free to reach out and provide me more info about the process. I have to read this book for sure. 📚

# macOS
swiftc \
        -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk \
        -F /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks \
        -I /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib \
        -L /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib \
        -L /Users/tib/swiftfromc/ \
        -module-name Example \
        -emit-executable \
        -Xlinker -rpath \
        -Xlinker @loader_path @/Users/tib/swiftfromc/LinkFileList \
        -Xlinker -rpath \
        -Xlinker /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \
        -Xlinker -rpath \
        -Xlinker /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx \
        -target arm64-apple-macosx12.1 \
        -Xlinker -add_ast_path \
        -Xlinker /Users/tib/swiftfromc/mytype.swiftmodule \
        -L /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib

# Linux
swiftc \
    -L /home/ec2-user/swiftfromc \
    -module-name Example \
    -emit-executable \
    -Xlinker -rpath \
    -Xlinker @loader_path @/home/ec2-user/swiftfromc/LinkFileList

The command above will produce the final linked executable file that you can run by using the ./Example snippet and hopefully you'll see the "Hello, World! 69" message. 🙈

If you want to know more about the rpath linker flag, I highly recommend reading the article by Marcin Krzyzanowski. If you want to read more about Swift / Objective-C interoperability and using the swiftc command, you should check out this article by RDerik. Finally if you want to call C code from Swift and go the other way, you should take a look at my other blog post.

Share this article
Thank you. 🙏

Get the Practical Server Side Swift book

Swift on the server is an amazing new opportunity to build fast, safe and scalable backend apps. Write your very first web-based application by using your favorite programming language. Learn how to build a modular blog engine using the latest version of the Vapor 4 framework. This book will help you to design and create modern APIs that'll allow you to share code between the server side and iOS. Start becoming a full-stack Swift developer.

Available on Gumroad
Picture of Tibor Bödecs

Tibor Bödecs

CEO @ Binary Birds

Server side Swift enthusiast, book author, content creator.