C Module for Swift, Swift Script and Dynamic Library Call 简体中文
Rockford Wei,2017-01-17
Last update: 2018-01-23
This project demonstrates how to call a customized C library in your swift project. You can clone it from github, or follow the instruction below to generate it step by step.
Furthermore, this demo also shows how to use swift as a script, with examples of calling C api as dynamic libraries. This practice makes it possible to patch hot fixes for server side swift without stopping the server, theoretically.
There are two C files: CSwift.c and CSwift.h. The objective is to build a CSwift library and export it to Swift.
Please compile this project with Swift 4.0.3 toolchain.
$ git clone https://github.com/RockfordWei/CSwift.git
$ cd CSwift
$ swift build
$ swift test
The sources are written in C while the test program is written in Swift. So if all tests passed, then congratulations! You've already mastered the process to call C api in a Swift source.
However, even without the sources above, you can still start everything from blank:
Assume the objective library is still CSwift, then find an empty folder and try these commands in a terminal:
mkdir CSwift && cd CSwift && swift package init
mkdir Sources/CSwift/include && rm Sources/CSwift/CSwift.swift
The above commands will set up the empty project template
Now edit the header file Sources/CSwift/include/Swift.h
:
extern int c_add(int, int);
#define C_TEN 10
Finishing the implementation of the C body Sources/CSwift/CSwift.c
:
#include "include/CSwift.h"
int c_add(int a, int b) { return a + b ; }
Next, we will setup an umbrella file for swift:
Sources/CSwift/include/module.modulemap
module CSwift [system] {
header "CSwift.h"
export *
}
Now let's check if the library works by editing a test script:
Tests/CSwiftTests/CSwiftTests.swift
import XCTest
@testable import CSwift
class CSwiftTests: XCTestCase {
func testExample() {
let three = c_add(1, 2)
XCTAssertEqual(three, 3)
XCTAssertEqual(C_TEN, 10)
}
static var allTests : [(String, (CSwiftTests) -> () throws -> Void)] {
return [ ("testExample", testExample) ]
}
}
The final step is the easiest one - build & test:
$ swift build
$ swift test
If success, then perfect!
Beside the above classic static build & run, Swift also provide an interpreter to execute swift source as scripts, just like a playground in a terminal. This project also makes an example for swift script, and even more, introduces how to call the same C api dynamically in such a script.
The default linking object of Swift 4 is static, so it needs a bit modification to turn it into a dynamic one.
To do this, edit the Package.swift file, and add a line:
.library(
name: "CSwift",
type: .`dynamic`, // <------------ Insert the dynamic type right here!
targets: ["CSwift"]),
Please check a swift script dll.swift.script
, actually it is a common swift with no difference to any other swift sources:
// First thing first, make sure your dll path is an dynamic library in an ABSOLUTE path.
// on Mac, the suffix is ".dylib"; on Linux, it is ".so"
guard let lib = dlopen(dllpath, RTLD_LAZY) else {
exit(0)
}
// declare the api prototype to call
typealias AddFunc = @convention(c) (CInt, CInt) -> CInt
// look up the function in the library
guard let c_add = dlsym(lib, "c_add") else {
dlclose(lib)
exit(0)
}
// attache the function to the real API address
let add = unsafeBitCast(c_add, to: AddFunc.self)
// call the C method, dynamically
let x = add(1, 2)
print(x)
// release resources
dlclose(lib)
This project also provides a bash script dll.sh
to run the swift script above.
# step one, build the C library
swift build
# then test what OS it is: .dylib for apple and .so for linux
if swift --version|grep apple
then
SUFFIX=dylib
else
SUFFIX=so
fi
# generate the full path of new library.
DLL=$PWD/.build/debug/libCSwift.$SUFFIX
# run the swift script and call the libray.
swift dll.swift.script $DLL
If Xcode is preferred, then try command swift package generate-xcodeproj
before building.