It’s been more than five years since I first tried Swift, and the language has changed dramatically from the first version of Swift to Swift 5.2. The Swift ecosystem has also been well developed, with Swift supported by almost all libraries used in daily development. For those of you who are still debating whether to use Swift or not, check out this article. The problems mentioned in the article almost cover some of the problems encountered when OC and Swift are mixed, and the corresponding solutions are provided in the article.

There is very little resistance to mixing Swift with Objective-C and C and C++ (Swift cannot call C++ directly, but must do so through OC). It can automatically bridge objective-C types, and even many C types. This allowed us to develop a simple, easy-to-use API using Swift based on the existing library. Swift and Objective-C have been written extensively, and in this article, we’ll learn how to make C interact with Swift.

Bridging Header

When we add C source files to a Swift project, Xcode asks if we want to add objective-C bridge files, just as we add OC files to a Swift project. Then we just need to add the Header files that need to be exposed to Swift code in the Bridging Header:

#include "test.h"
Copy the code

We declare a hello function in test.h:


#ifndef test_h
#define test_h

#include <stdio.h>
 void hello(void);  #endif /* test_h */ Copy the code

We then implemented it in TESh.c:

#include "test.h"

void hello(a) {
    printf("Hello World");
}
 Copy the code

Now we can call Hello () in our Swift code.

Swift Package Manager

The Bridging header approach above applies primarily to C source code in the same App target as Swift code, but not to Swift Framework which is separate, In this case, the Swift Package Manager (HEREAFTER SPM) is required. From Swift 3.0 we can use SPM to build C language targets.

Now we’ll use Swift to package an easy-to-use OpenGL library. With this example, we have basically learned how to interact with C in a Swif library.

Set the SPM

Setting up a Swift package manager project for importing C libraries is not difficult, but there are several steps to complete.

Now let’s start creating a new SPM project. Switch the directory where you want to save your code and create an SPM package by executing the following command:

$ mkdir OpenGLApp
$ cd OpenGLApp
$ swift package init --type library
Copy the code

We created a Swift library called OpenGLApp using the swift Package init –type library command. We can open the package. swift file to see what’s inside (with extraneous content removed) :

/ / swift - tools - version: 5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(  name: "OpenGLAPP". products: [  .library(name: "OpenGLApp", targets: ["OpenGLApp"]) ]. dependencies: [],  targets: [  .target(  name: "OpenGLApp". dependencies: [],  ] ) Copy the code

To complete a runnable OpenGL program, we need to rely on the C libraries GLFW and GLEW. GLFW gives us a window and context to render so we don’t have to write operating system code. GLEW provides a mechanism for determining the efficient running time of its OpenGL extension support on the target platform.

Export C libraries as modules

Since GLFW and GLEW are both C libraries, we need to figure out how to get Swift to find these C libraries so that Swift can call them. In C, you can access one or more libraries by #include their headers. Swift, however, cannot handle C headers directly; it relies on modules. In order for a library written in C and Objective-C to be visible to the Swift compiler, they must provide a Module map in the Clang Module format. Its main function is to list the header files that make up the module.

Since GLFW and GLEW do not provide module maps, we need to define a specific goal to generate module maps in SPM. What it does is encapsulate the above C libraries into modules that can be called in another Swift module.

First, we need to install Glew and GLFW, which can be installed using Homebrew for macOS. Other systems can be installed using the relevant package manager.

Then open package. swift and add the following information to targets:

.targets: [
.    .systemLibrary(
        name: "Cglew". pkgConfig: "glew". providers: [  .brew(["glew"])  ]),  .systemLibrary(  name: "Cglfw". pkgConfig: "glfw3". providers: [  .brew(["glfw"])  ]), ] Copy the code

In package. swift above, we added two new System Library targets. System library targets are those installed by system-level package managers, such as the ones we installed using Homebrew. Sample target is the final executable program, OpenGLApp is the OpenGL library that we will use Swift to encapsulate, and Cglew and Cglfw are the two system program targets that we made that can be called in Swift.

PkConfig: providers: pkConfig: providers: providers: pkConfig: providers: providers:

  • providersThe directive is optional and gives the SPM a hint of the way to install the library when the target library is not installed.
  • pkConfigSpecifies thepk g-configThe name of the file that Swift package manager can use to find the header file and library search path for the library to import. PkConfig name we can use in the library installation pathlib/pkconfig/xxx.pcIn the case of Glew installed on my computer, its location is/ usr/local/Cellar/glew 2.1.0 / lib/pkgconfig glew. The PC, so pkConfig is set aboveglew.

Next we need to create a directory to hold the files for the system library target in the Sources directory with the same name as the target’s name attribute defined above in package.swift. Here I take Cglfw as an example:

$ cd Sources && mkdir Cglfw
Copy the code

Add a glfw.h file to the Cglfw directory and add the following:

#include <GLFW/glfw3.h>
Copy the code

Then add a module.modulemap file, which should look like this:

module Cglfw [system] {
    header "glfw.h"
    export *
}
Copy the code

We added the glfw.h file to get around the restriction that absolute paths must be included in the modulemap, otherwise we would have to specify the absolute path of the glfw3.h header file in the header in the modulemap file. On my computer is/usr/local/Cellar/GLFW 3.3.2 rainfall distribution on 10-12 / include/GLFW/glfw3 h, thus will GLFW path hardcoded into the module in the map. Using the glfw.h file we added, SPM reads the correct header search path from the PKg-config file and adds it to the compiler’s call.

We can export GLEW as a module in the same way, which I won’t demonstrate here. In some cases, we only have the source code of the C library. In this case, we can still use SPM to export the source code of the C program as a module.

C source code exported as modules

Exporting C source code as a module is also very simple. It is actually the process of writing a module map, but this process can be done automatically for us with the help of SPM.

You can download the source code for GLEW here. As in the previous steps, create a Cglew subdirectory in the Sources directory and copy the include and SRC directories from the extracted GLEW source code into the Cglew directory. Then we add the following to package.swift:

.target(name: "Cglew")
Copy the code

We didn’t write module maps in the above process, not that we didn’t need module maps in this way, but SPM did it for us automatically. We put the headers we need to expose to the outside world into the include directory, and SPM automatically generates the module map at compile time. Of course, we can also specify the path to the external header file with the publicHeadersPath parameter.

And then we can do our goal of OpenGLApp. Add a glapp. swift file to the OpenGLApp directory. Now we can use import Cglew, import Cglfw, and call the APIS provided in GLFW and GLEW in our Swift file. Add “OpenGLApp” to your package. swift dependencies:

.target(
    name: "OpenGLApp".    dependencies: ["Cglfw"."Cglew"].    linkerSettings: [
        .linkedFramework("OpenGL")
 ]), Copy the code

To make it easy to write and debug programs in Xcode, you can generate an Xcode project using the swift package generate-Xcodeproj command.

After importing the Cglew module by importing Cglew and building the project, you will find that Xcode is reporting a lot of errors. Add #define GLEW_NO_GLU at the top of the glew.h file in the Cglew target.

The main work is to write The OpenGL code, but I won’t go into it here, because it’s not the focus of this article.

We can then add a target for an executable that runs the library. We add the Sample subdirectory to the Sources directory, add a main.siwft file, and add the targets Sample target to package. swift:

.target(
 name: "Sample".  dependencies: ["OpenGLApp"]),
Copy the code

Call OpenGLApp’s Swift library in main.siwft:

import OpenGLApp

let app = GLApp(title: "Hello, OpenGL", width: 600, height: 600)
app.run()
Copy the code

The SPM treats the target that contains the main.swift file as the executable target. So when we develop libraries with SPM, we should not have the main.swift file in the library file, otherwise SPM will treat the target as an executable instead of a library and will not be able to link to other libraries or executables properly.

If we continue to execute the swift run command in the terminal, then the SPM will build and execute the application (you can find the code for the shader here, and the code for initializing the vertex data here).

Hello,window

Here is the complete package.swift:

/ / swift - tools - version: 5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(  name: "GLAPP". products: [  .library(name: "OpenGLApp", targets: ["OpenGLApp"]) ]. dependencies: [],  targets: [  .target(  name: "Sample". dependencies: ["OpenGLApp"]),  .target(  name: "OpenGLApp". dependencies: ["Cglew"."Cglfw"]. linkerSettings: [  .linkedFramework("OpenGL")  ]),  .systemLibrary(  name: "Cglew". pkgConfig: "glew". providers: [  .brew(["glew"])  ]),  .systemLibrary(  name: "Cglfw". pkgConfig: "glfw3". providers: [  .brew(["glfw"])  ]),  ] ) Copy the code

To sum up, for the Swift module to be able to call C programs, all you need to do is export the C program code as a module. Exporting a Module only needs to provide a Module map in the Clang Module format.

review

Using C program code in Swift code is actually a very simple matter, rather than using Swift to rewrite an existing C library, why not just use them in Swift. Of course, there will be some problems in the actual use of C, such as Pointers, callback functions, etc., but these are not big problems, not knowing how to use them just shows that we are not familiar with some aspects of Swift.

This article is formatted using MDNICE