c语言字符串传给swift,如何把字符串数组从 Swift 传递给 C
作者:Natasha The Robot,原文鏈接,原文日期:2016-10-27
譯者:BigbigChai;校對:walkingway;定稿:CMB
Swift 允許我們將原生的字符串直接傳遞給一個接受 C String(即 char *)的 C API。 比如說,你可以在 Swift 里調(diào)用 strlen 函數(shù),如下所示:
import Darwin // or Glibc on Linux
strlen("Hello ?") // → 10
雖然在 Swift 中,const char * 參數(shù)是作為 UnsafePointer ! 導入的,但這的確可行。 Swift 導入的 strlen 函數(shù)的完整類型定義如下:
func strlen(_ __s: UnsafePointer!) -> UInt
類型檢查器能夠 將 String 值傳遞給一個 UnsafePointer 或 UnsafePointer 參數(shù) 。在此過程中,編譯器隱式地創(chuàng)建了一個緩沖區(qū),它包含一段以 UTF-8 編碼null 結(jié)束的字符串,并傳回一個指向緩沖區(qū)的指針給函數(shù)。
對 C 字符串數(shù)組沒有內(nèi)置支持
Swift 處理單個 char * 參數(shù)的方式非常簡便。但是,一些 C 函數(shù)接收字符串數(shù)組(一個 char * 或 char * [])作為參數(shù),而 Swift 對將 [String] 傳遞給一個 char * 參數(shù)并沒有內(nèi)置支持。
一個實用的例子是子進程啟動時的 posix_spawn 函數(shù)。 posix_spawn 的最后兩個參數(shù)(argv 和 envp)是用于傳遞新進程的參數(shù)和環(huán)境變量的字符串數(shù)組。文檔中是這么說明的:
argv(和 envp)是指向以 null 結(jié)尾的字符串數(shù)組指針,數(shù)組元素指向以 null 結(jié)束的字符串。
Swift 將這些參數(shù)中 C 類型的 char * const argv [] 轉(zhuǎn)換為難以處理的 UnsafePointer ?>!,感嘆號表示 對可選值隱式解包 ,告訴我們 API 這里的參數(shù)不能為空,即 Swift 不知道函數(shù)是否接受傳遞 NULL(在這種情況下外層 UnsafePointer 將為可選值)。我們必須參考文檔來回答這個問題。在本示例中,文檔明確聲明了 argv 必須至少包含一個元素(生成程序的文件名)。 envp 可以為 NULL ,表示它將繼承其父進程的環(huán)境。
將 Swift 字符串數(shù)組轉(zhuǎn)換為 C 字符串數(shù)組
假設我們想為 posix_spawn
/// 產(chǎn)生一個子進程
///
/// - Returns: A pair containing the return value of `posix_spawn` and the pid of the spawned process.
func spawn(path: String, arguments: [String]) -> Int32
現(xiàn)在我們需要將參數(shù)數(shù)組轉(zhuǎn)換為 posix_spawn 能夠接收的格式。 這需要幾個步驟:
以 UTF-8 編碼字符串元素。
為每個 UTF-8 編碼的字符串的末尾添加一個空字節(jié)。
將所有 UTF-8 編碼的、以空字節(jié)結(jié)尾的字符串拷貝到一個緩沖區(qū)中。
在緩沖區(qū)的末尾添加另一個空字節(jié),表明 C 數(shù)組的結(jié)尾。
確保緩沖區(qū)存在于 posix_spawn 被調(diào)用的整個生命周期內(nèi)。
withArrayOfCStrings 在標準庫中
Swift 團隊也需要使用這個功能來運行標準庫的單元測試,因此標準庫的源也包括一個名為 withArrayOfCStrings 的函數(shù)。現(xiàn)在這是一個私有函數(shù),不公開暴露給 stdlib 使用者(雖然它被聲明為 public,大概因為不這么做的話單元測試無法看到它)。但這個函數(shù)依然對我們可見。這是該函數(shù)的接口:
public func withArrayOfCStrings(
_ args: [String],
_ body: ([UnsafeMutablePointer?]) -> R
) -> R
它具有與 withUnsafePointer 及其變體相同的形式:它的結(jié)果類型 R 是一個泛型,并且接收一個閉包作為參數(shù)。其思想是,在將字符串數(shù)組轉(zhuǎn)換為 C 數(shù)組之后, withArrayOfCStrings 調(diào)用閉包,傳遞 C 數(shù)組,并將閉包的返回值轉(zhuǎn)發(fā)給其調(diào)用者。這使得 withArrayOfCStrings 函數(shù)完全控制它自己創(chuàng)建緩沖區(qū)的生命周期。
我們現(xiàn)在可以這樣實現(xiàn) spawn 函數(shù):
/// Spawns a child process.
///
/// - Returns: A pair containing the return value of `posix_spawn` and the pid of the spawned process.
func spawn(path: String, arguments: [String]) -> (retval: Int32, pid: pid_t) {
// Add the program's path to the arguments
let argsIncludingPath = [path] + arguments
return withArrayOfCStrings(argsIncludingPath) { argv in
var pid: pid_t = 0
let retval = posix_spawn(&pid, path, nil, nil, argv, nil)
return (retval, pid)
}
}
為什么這是可行的呢?能注意到 withArrayOfCStrings 的閉包參數(shù)的類型為 ([UnsafeMutablePointer?]) -> R 。參數(shù)類型 [UnsafeMutablePointer ?] 看起來與 posix_spawn 要求的 UnsafePointer ?>! 并不兼容,但其實是兼容的。CChar 只是 Int8 的別名。再者,正如 Swift 對于傳遞給 C 的字符串會有特殊處理,編譯器隱式地將原生 Swift 數(shù)組傳遞給接收 UnsafePointer 參數(shù)的 C 函數(shù)。因此我們可以將數(shù)組直接傳遞給 posix_spawn,只要它的元素類型與指針指向元素的類型相匹配。
這是使用 spawn 函數(shù)的樣例:
let (retval, pid) = spawn(path: "/bin/ls", arguments: ["-l", "-a"])
這是執(zhí)行程序的輸出:
$ swift spawn.swift
posix_spawn result: 0
new process pid: 17477
total 24
drwxr-xr-x 4 elo staff 136 Oct 27 17:04 .
drwx---r-x@ 41 elo staff 1394 Oct 24 20:12 ..
-rw-r--r--@ 1 elo staff 6148 Oct 27 17:04 .DS_Store
-rw-r--r--@ 1 elo staff 2342 Oct 27 15:28 spawn.swift
(注意,如果你在 playground 中調(diào)用它,posix_spawn 會返回一個錯誤,可能是因為 playground 的沙盒不允許生成子進程。因此最好通過命令行創(chuàng)建,或在 Xcode 中創(chuàng)建一個新的命令項目)。
工作原理
public func withArrayOfCStrings(
_ args: [String], _ body: ([UnsafeMutablePointer?]) -> R
) -> R {
let argsCounts = Array(args.map { $0.utf8.count + 1 })
let argsOffsets = [ 0 ] + scan(argsCounts, 0, +)
let argsBufferSize = argsOffsets.last!
var argsBuffer: [UInt8] = []
argsBuffer.reserveCapacity(argsBufferSize)
for arg in args {
argsBuffer.append(contentsOf: arg.utf8)
argsBuffer.append(0)
}
return argsBuffer.withUnsafeMutableBufferPointer {
(argsBuffer) in
let ptr = UnsafeMutableRawPointer(argsBuffer.baseAddress!).bindMemory(
to: CChar.self, capacity: argsBuffer.count)
var cStrings: [UnsafeMutablePointer?] = argsOffsets.map { ptr + $0 }
cStrings[cStrings.count - 1] = nil
return body(cStrings)
}
}
讓我們逐行解說。第一行為輸入的字符串創(chuàng)建一個 UTF-8 編碼的字符計數(shù)(加上為空的終止標識的一字節(jié))的數(shù)組:
let argsCounts = Array(args.map { $0.utf8.count + 1 })
下一行讀取這些字符計數(shù),并計算每個輸入字符串的字符偏移量,即每個字符串將在緩沖區(qū)中的開始位置。第一個字符串當然將被定位在偏移量為零的地方,并通過累積字符計數(shù)來計算后續(xù)偏移量:
let argsOffsets = [ 0 ] + scan(argsCounts, 0, +)
代碼使用一個名為 scan 的幫助函數(shù),它定義在同一個文件里。注意,argsOffsets 包含的元素數(shù)量比 argsCounts 多一個。因為 argsOffsets 的最后一個元素是最后一個輸入字符串之后的偏移量,即所需的緩沖區(qū)的大小。
下一步是創(chuàng)建一個字節(jié)數(shù)組(元素類型為 UInt8)用作緩沖區(qū)。由于緩沖區(qū)會自動增長,因此調(diào)用 reserveCapacity 不是必要的。但如果在開始時能事先知道的所需容量并保留的話,可以避免重復的分配行為:
let argsBufferSize = argsOffsets.last!
var argsBuffer: [UInt8] = []
argsBuffer.reserveCapacity(argsBufferSize)
現(xiàn)在可以將 UTF-8 編碼的字節(jié)寫入緩沖區(qū),并在每個輸入的字符串后添加一個空字節(jié):
for arg in args {
argsBuffer.append(contentsOf: arg.utf8)
argsBuffer.append(0)
}
此時,我們有一個正確格式的字節(jié)數(shù)組(UInt8)。我們?nèi)匀恍枰獦?gòu)造指向緩沖區(qū)中的元素的指針數(shù)組。這就是函數(shù)最后一部分的作用:
return argsBuffer.withUnsafeMutableBufferPointer {
(argsBuffer) in
let ptr = UnsafeMutableRawPointer(argsBuffer.baseAddress!).bindMemory(
to: CChar.self, capacity: argsBuffer.count)
var cStrings: [UnsafeMutablePointer?] = argsOffsets.map { ptr + $0 }
cStrings[cStrings.count - 1] = nil
return body(cStrings)
}
我們利用 withUnsafeMutableBufferPointer 獲得數(shù)組,其元素表示指向緩沖區(qū)的指針。內(nèi)部閉包的第一行代碼通過 UnsafeMutableRawPointer 將元素指針的類型從 UnsafeMutablePointer 轉(zhuǎn)換為 UnsafeMutablePointer 。 (從 Swift 3.0 開始,你不能直接在類型化的指針之間進行轉(zhuǎn)換,你必須首先轉(zhuǎn)換成 Unsafe[Mutable] RawPointer 。)這段代碼的可讀性不是很好,但對我們來說這行之后的內(nèi)容才是重要的。本地 ptr 變量是指向緩沖區(qū)中的第一個字節(jié)的 UnsafeMutablePointer。
現(xiàn)在,為了構(gòu)造指針數(shù)組,我們?yōu)榈诙兄袆?chuàng)建的字符偏移數(shù)組做映射,并根據(jù)每個偏移量向后移動指針。最后將結(jié)果數(shù)組中的最后一個元素設置為 nil,用作表示數(shù)組結(jié)尾的空指針(記得我們之前說的 argsOffset 要比輸入數(shù)組包含多一個元素嗎?因此重寫最后一個元素是正確的)。
最后,我們可以調(diào)用從調(diào)用者傳遞過來的閉包,傳遞指向 C 字符串的指針數(shù)組。
本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請訪問 http://swift.gg。
注意,由于上面的 emoji 是以 UTF-8 格式傳遞的,它在 strlen 函數(shù)里會占用四個“字符“。 ?
在這里使用了 posix_spawn 作為簡單的例子來講解。但在生產(chǎn)代碼中,應該使用 Foundation 框架里更高級的 Process 類(née NSTask)來實現(xiàn)。 ?
總結(jié)
以上是生活随笔為你收集整理的c语言字符串传给swift,如何把字符串数组从 Swift 传递给 C的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: apache php日志配置,HTML_
- 下一篇: linux进程运行队列,Linux进程调