Link Static C Library to Swift Framework As A Private Module

Yuliia Synytsia
6 min readJul 23, 2019

--

Photo by Thomas Kelley on Unsplash

A brief overview of how to link C code or compiled code with your Swift Framework

Problem

Let’s assume you have a static library(.a, .h)/source code(.c, .h)/dynamic library (.dylib, .h) (it really doesn’t matter when it comes to frameworks) written in C with “secret sauce” of your product and you want to wrap it up in Swift framework and distribute via Cocoapods/Carthage/Swift Packager.

Despite how easy and encouraging is open-source distribution nowadays, sometimes we don’t want to expose all our APIs to the world.

In this article, I’ll show one of the ways of creating Public and Private APIs for Swift framework.

C — Swift linking

A brief overview of how to link C code or compiled the code with your Swift Framework.
There are two ways to link C code to your Swift project:

  1. Bridging-Header.h is ONLY for Applications and Unit Tests
  2. Module map and header files are for frameworks

If you try to use Bridging-Header.h in your framework Xcode will give you an error message like that:

using bridging headers with framework targets is unsupported

Let’s talk about Clang and Module Map

Clang documentation is very interesting long read you should find time for https://clang.llvm.org/docs/Modules.html#introduction. Only if you’re interested in Xcode “magic” build process.

Modules provide an alternative, simpler way to use software libraries that provides better compile-time scalability and eliminates many of the problems inherent to using the C preprocessor to access the API of a library.

Basically, clang turns libraries into “modules” and instead of line in your bridging header file:

#include <someLib.h>

you just import module in your Swift code:

import yourFramework.someLib

or Objective-C:

@import yourFramework.someLib;

Modules improve access to the API of software libraries by replacing the textual preprocessor inclusion model with a more robust, more efficient semantic model. — Clang Documentation

And all Swift users like: “yay! no more bridging header!”, “yay! a more robust, more efficient semantic model”.

But wait for it… More fun to come.

I didn’t mean to do deep dive in Module maps in this article (maybe next time?). So I’ll briefly describe what you need to do to make it compile and maybe even work >_<.

Xcode won’t magically create module maps for you,… or wait… It partly does, how and why I’ll explain later.
So you have to create a module.modulemap file for your library, just like so:

/* module.modulemap */module myLib {   header “header1.h”   export *}

WARNING: module.modulemap should be in the same directory as compiled library(.a) and headers (.h). Btw the same for source code(.c, .cpp).

And add a header file to your Xcode project.

This section of the article is not meant to be a step by step tutorial (you can find a lot of good articles about it, like this one)

Package C library with Swift Framework

Now we know how to add our compiled library to our framework. But how we’re going to distribute it all together while keeping our C library API to ourselves?

First, we should understand how the framework packaging works. It doesn’t matter which packaging system you use Cocoapods/Carthage/Swift Packager you have to provide all necessary info (linking, headers) about your framework and its dependencies while it’s archiving.

When you build a framework, as an output Xcode creates “yourProject.framework” archive. Usually, you can observe it under “Products” in Xcode’s Project Navigator Section.

Right-click on it → Show in Finder and you can see all files that going to be distributed along with your code or binary.

Out of curiosity open “module.modulemap” file, it should be the same file we created when we added our library, right?

But wait,… it’s not! why?

framework module swiftLinearAlgebra {   umbrella header “swiftLinearAlgebra.h”   export *   module * { export * }}

I don’t remember having this code in my project.

The answer is simple: remember I mention something about Xcode partly generates module maps? Yep, this alien is a creation of Xcode. And how you can see, there’s no mention of our library here, so if you’ll send this framework to your customer App you’ll get a compilation error:

“Missing required module”

Let’s fix it:

Xcode generates this file if Module Map File value is not set in Build Settings of the project. By default it’s empty.

We’re going to fix it in 4 simple steps:

Step 1: Create <yourProjectName>.modulemap file. With the same content as autogenerated one:

framework module swiftLinearAlgebra {   umbrella header “swiftLinearAlgebra.h”   export *   module * { export * }}

WARNING: name of the module map should match with Product Module Name value in Build Settings.

Step 2. Add our module (C library)

framework module swiftLinearAlgebra {   umbrella header “swiftLinearAlgebra.h”   export *   module * { export * }   explicit module LinearAlgebra {      header “c_dgesv.h”      link “LinearAlgebra”      export *   }}

where:

swiftLinearAlgebra is a framework I’m going to distribute.

LinearAlgebra is an internal static library.

Step 3: Set Module Map File path in Build Settings.

Step 4: Add headers. Navigate to Build Phases and make sure all your headers listed there:

Voila! Done.

Try it now. “Missing required module” should be gone now.

But wait, customer app has access to C library API.

Let’s hide it.

Public and Private API’s

Ok, how to hide our C library so clang knows about it but our customer app doesn’t?

Clang documentation has a section about module.private.modulemap files: Private Module Map Files

But I’m going to disappoint you, … , it’s not going to work with frameworks.

W#&!
I know… right?!

Let’s make it work:

Step 1: Rename module.modulemap to <yourFrameworkName>.modulemap.

Step 2: Modify<yourFrameworkName>.modulemap to:

framework module swiftLinearAlgebra {   umbrella header “swiftLinearAlgebra.h”   export *   module * { export * }

// C library
explicit module swiftLinearAlgebra_Private { header “c_dgesv.h” link “LinearAlgebra” export * }}

NOTE: name of your private module should be <yourProjectName>_Private

Clang has extra logic to work with this naming, using FooPrivate or Foo.Private (submodule) trigger warnings and might not work as expected.

Step 2: Move private headers to Private section in Build Phases:

Now it’s accessible from your Swift framework, but not from outside:

import Foundationimport swiftLinearAlgebra.swiftLinearAlgebra_Privatepublic class LinearAlgebra {   public class func swift_dgesv( a: inout [Double],  b: inout [Double]) -> UnsafeMutablePointer<Int32>? {

// Private C libarary API
return c_dgesv(UnsafeMutablePointer<Double>(&a), UnsafeMutablePointer<Double>(&b)) }}

Conclusion

I just leave it here:

The module map language is not currently guaranteed to be stable between major revisions of Clang.Resources — Clang Documentation

Till next time…

Resources

1. Clang Documentation

2. Swift Documentation

3. GitHub Full Example

4. Using a C library inside a Swift framework by Roy Marmelstein

5. Bonus: Distributing Compiled Swift Frameworks via Cocoapods by Anurag Ajwani

--

--