Swift 的 C 模块、Swift 脚本和动态库调用 简体中文

Rockford Wei,2017-01-17

最后更新: 2018-01-23

此项目演示了如何在 Swift 项目中调用自定义 C 库。您可以从 github 克隆它,或者按照下面的说明逐步生成它。

此外,此演示还展示了如何将 Swift 用作脚本,并提供了调用 C API 作为动态库的示例。从理论上讲,这种做法使得无需停止服务器即可为服务器端 Swift 打热补丁成为可能。

简介

这里有两个 C 文件:CSwift.c 和 CSwift.h。目标是构建一个 CSwift 库并将其导出到 Swift。

快速开始

请使用 Swift 4.0.3 工具链编译此项目。

构建 & 测试

$ git clone https://github.com/RockfordWei/CSwift.git
$ cd CSwift
$ swift build
$ swift test

源代码是用 C 编写的,而测试程序是用 Swift 编写的。因此,如果所有测试都通过了,那么恭喜您!您已经掌握了在 Swift 源代码中调用 C API 的过程。

逐步讲解

然而,即使没有上面的源代码,你仍然可以从空白开始。

从空文件夹开始

假设目标库仍然是 CSwift,那么找到一个空文件夹并在终端中尝试这些命令

mkdir CSwift && cd CSwift && swift package init
mkdir Sources/CSwift/include && rm Sources/CSwift/CSwift.swift

上面的命令将设置空的项目模板

C 头文件

现在编辑头文件 Sources/CSwift/include/Swift.h

extern int c_add(int, int);
#define C_TEN 10

C 源代码

完成 C 主体的实现 Sources/CSwift/CSwift.c

#include "include/CSwift.h"
int c_add(int a, int b) { return a + b ; }

模块映射

接下来,我们将为 Swift 设置一个 umbrella 文件: Sources/CSwift/include/module.modulemap

module CSwift [system] {
  header "CSwift.h"
  export *
}

在 Swift 中调用 C API

现在让我们通过编辑测试脚本来检查库是否工作: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)  ]
  }
}

测试

最后一步是最简单的一步 - 构建 & 测试

$ swift build
$ swift test

如果成功,那就完美了!

Swift 作为脚本并动态调用 C 库

除了上述经典的静态构建和运行之外,Swift 还提供了一个解释器来执行 Swift 源代码作为脚本,就像终端中的 playground 一样。 这个项目还提供了一个 Swift 脚本示例,更重要的是,介绍了如何在这样的脚本中动态调用相同的 C API。

动态链接库

Swift 4 的默认链接对象是静态的,因此需要进行一些修改才能将其转换为动态的。

为此,请编辑 Package.swift 文件,并添加一行

.library(
    name: "CSwift",
    type: .`dynamic`,  // <------------ Insert the dynamic type right here!
    targets: ["CSwift"]),

请检查一个 Swift 脚本 dll.swift.script,实际上它是一个普通的 Swift,与任何其他 Swift 源代码没有区别。

// 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)

运行 Swift 脚本

此项目还提供了一个 bash 脚本 dll.sh 来运行上面的 Swift 脚本。

# 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

更多信息

如果首选 Xcode,请在构建之前尝试命令 swift package generate-xcodeproj