How to embed C-library in Swift-framework

Original author: Sergey Lem
  • Transfer


In 2014, Swift was introduced, a new language for developing Apple's ecosystem applications. The novelty brought not only new features and functions, but also problems - to those who wanted to use the good old C-libraries. In this article I will look at one of them - the C-library banding in a Swift framework. There are several ways to solve it; in this case, I will explain how to do this with the help of clang explicit modules.

For example, we take the external C library libgif and embed it in our Swift framework GifSwift. If you want to see the result immediately, you can see the full project here .

Libgif preparation


Before embedding the libgif library into our project, it should be compiled from sources.

  1. Download the latest version of tarball here .
  2. Unpack the archive, use the console to go to the folder and run:

    ./configure && make check

    Note: for simplicity, we are building a library for the x86-64 platform, and therefore it will only work in an iOS simulator or on macOS. Building a multi-architecture static library is a separate topic that I don’t touch on in this article. Useful instructions can be found here .
  3. If everything goes smoothly, the library files can be found in ${lib_gif_source}/lib/.libs. We are interested in two files:

    lib/.libs/libgif.a # Статическая библиотека
    lib/gif_lib.h # Интерфейс
    

Project Setup


Now we will adjust the project for our needs.

  1. Create a new project using the template Cocoa Touch Framework, give it the name GifSwift .
  2. Add the libgif library files we created to a separate group within the project.
  3. Add a new target to the project for a test application to see the result.

The final project structure should look like this:



Import to Swift


In order to import the C library into Swift, we need to describe it as a module . The description is a .modulemap file containing a list of header files for import and static libraries for linking. The resulting module can be imported into Swift or Objective-C code (using @import).

This way of importing the library into the framework will work in most cases (for more details about this approach read here). It is great if you are creating an internal framework or simply breaking your application into modules. But this method also has disadvantages. For example, it is ineffective if you want to transfer your library to someone using Carthage, Cocoapods or in the sense of a binary artifact. The reason is that the resulting framework is generally not portable, because when compiled it is tied to a specific location of the header files and libraries from the module map on your computer.

Explicit module


To circumvent these limitations, we will use another way - the explicit module for the library. An explicit-module is a module that is declared a sub-module using the explicit keyword , is placed in the parent module and is not imported automatically. It works similarly *_Private.hfor Objective-C frameworks. If you want to use the APIs declared in it, you need to import the module explicitly.

We create an explicit module for the C-library inside the framework. To do this, we need to perform a redefinition of the generated XCode module. Also note that we do not specify the libgif.a library for linking (link gif), but instead do it right in the project using the Xcode interface.

Note: To learn more about explicit modules, please click here.

  1. Add a file called GifSwift.modulemap to the root folder of the project :

    framework module GifSwift {
        umbrella header "GifSwift.h"
        explicit module CLibgif {
            private header "gif_lib.h"
        }
        export *
    }
    

    This file contains the specification for the explicit CLibgif module and consists of one declared header file (since there is just one such in our library). The file is loaded into the resulting module for the framework.
  2. The file with the description of the module does not need to be added to the framework, but it must be specified in the settings of the target:

    Build Settings — Packaging — Module Map (MODULEMAP_FILE)
    =
    $SRCROOT/GifSwift/GifSwift.modulemap
  3. The libgif files must be added to the target framework in the form of a private header ( gif_lib.h ) and a static library ( libgif.a ). Note that the header file for the C library is added to the target as private. This is necessary for our explicit module. Nothing prevents to add this header file as public, but our task is to hide implementation details with the simplest possible means.


  4. Now you can import an explicit module inside the framework using import GifSwift.CLibgif

Swift-wrapper


Now you can do the interface of our framework. One class is enough that is a gif with a couple of properties:

import Foundation
import GifSwift.CLibgif
public class GifFile {
    private let path: URL
    private let fileHandlePtr: UnsafeMutablePointer<GifFileType>
    private var fileHandle: GifFileType {
        return self.fileHandlePtr.pointee
    }
    deinit {
        DGifCloseFile(self.fileHandlePtr, nil)
    }
    // MARK: - API
    public init?(path: URL) {
        self.path = path
        let errorCode = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
        if let handle = path.path.withCString({ DGifOpenFileName($0, errorCode) }) {
            self.fileHandlePtr = handle
            DGifSlurp(handle)
        } else {
            debugPrint("Error opening file \(errorCode.pointee)")
            return nil
        }
    }
    public var size: CGSize {
        return CGSize(width: Double(fileHandle.SWidth), height: Double(fileHandle.SHeight))
    }
    public var imagesCount: Int {
        return Int(fileHandle.ImageCount)
    }
}

GifFile.swift wraps low-level APIs for file processing and gains access to some properties, mapping them to more convenient Foundation types.

Check


In order to test our library, I added the file cat.gif to the project :

import UIKit
import GifSwift
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        if let file = GifFile(path: Bundle.main.url(forResource: "cat", withExtension: "gif")!) {
            debugPrint("Image has size: \(file.size) and contains \(file.imagesCount) images")
        }
    }
}

When you run this code in the console, we see the following:

" Image has size: (250.0, 208.0) and contains 44 images"

findings


The resulting framework contains everything you need to use, has a Swift-interface and by default hides the C-code from clients. However, this is not entirely true. As I wrote above, importing GifSwift.CLibgif , you get access to all closed modules, but by default this method of encapsulation is enough to hide the implementation details of the framework.

Also popular now: