跳转到内容
Skip 中文站
3.2k

外部函数接口(FFI)

这是一个 Skip Lite Swift/Kotlin 库项目,使 Skip 转译生成的 Kotlin 代码能够在 Android 上调用 C 和 C++ 库。

在 Kotlin 端,SkipFFI 使用 Java Native Access(JNA ↗)库,将 Swift.OpaquePointer 等 Swift 类型模拟为 com.sun.jna.Pointer 指针引用,并通过 Java 端的 com.sun.jna.ptr.PointerByReference 实现 Swift.withUnsafeMutablePointer。

SkipSQL 和 SkipScript 等 Skip 框架利用此能力,为 Darwin 和 Android 上的底层原生 C API 提供统一的 API。

要在项目中使用此框架,请将以下依赖项添加到 Package.swift 文件:

let package = Package(
name: "my-package",
products: [
.library(name: "MyProduct", targets: ["MyTarget"]),
],
dependencies: [
.package(url: "https://source.skip.dev/skip-ffi.git", from: "1.0.0"),
],
targets: [
.target(name: "MyTarget", dependencies: [
.product(name: "SkipFFI", package: "skip-ffi")
])
]
)
#if !SKIP
import Darwin
#else
import SkipFFI
let Darwin = BionicDarwin()
func BionicDarwin() -> BionicDarwin {
com.sun.jna.Native.load("c", (BionicDarwin.self as kotlin.reflect.KClass).java)
}
protocol BionicDarwin : com.sun.jna.Library {
func abs(_ value: Int32) -> Int32
func malloc(_ size: Int32) -> OpaquePointer
func free(_ ptr: OpaquePointer) -> Int32
func getenv(_ key: String) -> String?
}
#endif
// 完全限定的 Module.fname() 将通过 SkipFFI 调用 C 接口
Darwin.abs(-12) // 12
Darwin.free(Darwin.malloc(8))

SkipFFI 的实现提供以下定义:

public typealias OpaquePointer = com.sun.jna.Pointer
public typealias UnsafeMutableRawPointer = com.sun.jna.ptr.PointerByReference
public func withUnsafeMutablePointer<T>(to pointerRef: InOut<OpaquePointer?>, block: (UnsafeMutableRawPointer) throws -> T) rethrows -> T

SkipFFI 无法直接使用 Foundation 的 Data API。

如果需要访问原始字节,可以直接使用相关 API:

let blob = Data(…)
let size = blob.count
#if SKIP
let buf = java.nio.ByteBuffer.allocateDirect(size)
buf.put(blob.kotlin(nocopy: true)) // 传输字节
let ptr = com.sun.jna.Native.getDirectBufferPointer(buf)
try check(code: SQLite3.sqlite3_bind_blob(stmnt, index, ptr, size, nil))
#else
try blob.withUnsafeBytes { ptr in
try check(code: SQLite3.sqlite3_bind_blob(stmnt, index, ptr.baseAddress, size, nil))
}
#endif

借助 SkipFFI,你可以在双平台 Skip 框架中嵌入 C 代码,并使用 SkipFFI 为这些代码创建符合语言习惯的包装器,供 Swift 和转译生成的 Kotlin 共同使用。

SkipFFI 可以在转译生成的 Kotlin 与嵌入式 C 库之间提供直接接口。它会配置 Gradle 对 CMake 构建文件的支持以及 Android NDK 工具链,从而为 Android 支持的各种架构构建嵌入式 C 库。这与 Xcode 和 SwiftPM 针对不同架构构建 C 源代码并将其与 Swift 代码链接的方式非常相似。

有关如何使用 C 文件为 Swift 和 Kotlin 提供统一 API 的示例,请参阅 Skip C 演示 ↗项目。

直接通过 Xcode 构建并测试 skip-c-demo 项目,或在终端中使用 swift test 时,会执行常规的 Skip 测试流程:先针对 macOS 架构编译并运行 Swift 测试用例,再由特殊的 XCSkipTests 触发 SkipUnit 框架,对转译后的源文件和测试用例文件运行 gradle test。虽然此流程可透明地处理任何嵌入式 C 文件,但请注意,本地测试运行在 macOS 本机 JVM 上,因此并未真正使用交叉编译的 Android 原生库;它链接的是由 SwiftPM 在本地构建的 C 库。

这是测试 SkipFFI 原生桥接的最快方式。但如果 C 代码需要与仅在 Android 上可用的库交互,例如各种 NDK API(https://developer.android.com/ndk/guides/stable_apis ↗),就需要在实际的 Android 模拟器或设备上进行非本地测试。

要在真实 Android 系统上测试交叉编译的共享库,需要面向 Android 模拟器或设备运行插桩测试。首先从 Android Studio Device Manager 启动模拟器,然后使用终端命令 adb devices 获取设备标识符。如果设备 ID 为 “emulator-5554”,可以使用以下命令在该模拟器上运行转译后的测试:

ANDROID_SERIAL=emulator-5554 swift test

同样,也可以在待测试目标的 Xcode scheme 的 Run Arguments 页面中设置 ANDROID_SERIAL 环境变量,其效果相同:面向指定的模拟器或设备运行插桩测试。

本项目是一个 Swift Package Manager 模块,使用 Skip 插件为 iOS 和 Android 构建软件包。

可以使用标准的 swift test 命令测试此模块,也可以在 Xcode 中针对 macOS 目标平台运行测试目标。后者会同时运行 Swift 测试,以及在 Robolectric Android 模拟环境中运行转译后的 Kotlin JUnit 测试。

可以使用 skip test 执行一致性测试;该命令会以表格形式输出两个平台的测试结果。

欢迎通过功能增强和错误修复为此软件包作出贡献。

为此软件包及其他 Skip 软件包作出贡献的一般流程如下:

  1. Fork 此仓库,并在 “Actions” 标签页中启用 Actions
  2. 将你的 fork 检出到本地
  3. 与 Skip 应用一同开发时,将软件包添加到共享工作区,以便在应用中看到集成后的改动
  4. 将改动推送到你的 fork,并确保 Actions 标签页中的所有 CI 检查均通过
  5. 将你的名字添加到 Skip 贡献者协议 ↗中
  6. 从你的 fork 发起 Pull Request,并说明所作改动

skiptools/skip-ffi ↗ 最近发布的 10 个版本:

完整历史:github.com/skiptools/skip-ffi/releases ↗ — .atom 订阅源 ↗