文字列配列をSwiftからCに渡す方法
8285 ワード
作者:Natasha The Robot,原文リンク,原文日付:2016-10-27訳者:BigbigChai;校正:walkingway;原稿:CMB
Swiftは、C String(すなわち
Swiftでは
タイプインスペクタは、String値を
C文字列配列の組み込みサポートはありません
Swiftが単一の
実用的な例は、サブプロセス起動時のposix_です.spawn関数.posix_spawnの最後の2つのパラメータ(
Swiftは、これらのパラメータのうちCタイプの
Swift文字列配列をC文字列配列に変換
posix_spawn 2は優雅なSwiftインタフェースを提供します.カプセル化関数は、起動中のプログラムのパスと文字列配列のパラメータを受信する必要があります.
パラメータ配列をposix_に変換する必要がありますspawnが受信できるフォーマット.これにはいくつかのステップが必要です.は、文字列要素をUTF-8で符号化する. は、各UTF-8によって符号化された文字列の末尾に空のバイトを追加する. は、すべてのUTF-8符号化された、空のバイトで終わる文字列を1つのバッファにコピーする. バッファの最後に、C配列の最後を示す別の空のバイトを追加します. バッファがposix_に存在することを確認spawnが呼び出されたライフサイクル全体.
Swiftチームもこの機能を使用して標準ライブラリのユニットテストを実行する必要があるため、標準ライブラリのソースには
それはwithUnsafePointerとその変体と同じ形式を持っている:その結果タイプRは汎用であり、パラメータとして閉パケットを受信する.文字列配列をC配列に変換した後、
なぜこれが可能なのでしょうか.
これは実行プログラムの出力です.
(playgroundで呼び出すと、
さぎょうげんり
逐行解説しましょう.第1の動作で入力された文字列は、UTF-8によって符号化された文字数(空の終端識別子の1バイトを加えた)の配列を作成します.
次の行では、これらの文字数を読み取り、入力文字列ごとの文字オフセットを計算します.すなわち、各文字列がバッファ内の開始位置になるようにします.最初の文字列はもちろん、オフセット量がゼロの場所に配置され、文字数を累積することで後続のオフセット量を計算します.
コードはscanというヘルプ関数を使用し、同じファイルに定義されます.
次に、バッファとしてバイト配列(要素タイプ
このとき、正しいフォーマットのバイト配列(
我々はwithUnsafeMutableBufferPointerを用いて配列を取得し,その要素はバッファへのポインタを表す.内部閉パケットの第1行コードは、
次に、ポインタ配列を構築するために、2行目に作成した文字オフセット配列をマッピングし、各オフセット量に基づいてポインタを後方に移動します.最後に、結果配列の最後の要素を
最後に,呼び出し者から渡された閉パケットを呼び出し,C文字列を指すポインタ配列を渡すことができる.
本文はSwiftGG翻訳グループから翻訳して、すでに作者の翻訳の授権を得て、最新の文章は訪問して下さいhttp://swift.gg.上のemojiは ここでは簡単な例として
Swiftは、C String(すなわち
char *
)を受け入れるC APIに、オリジナルの文字列を直接渡すことを可能にする.たとえば、次のように、Swiftでstrlen関数を呼び出すことができます.
import Darwin // or Glibc on Linux
strlen("Hello ?") // → 10
Swiftでは
const char *
パラメータがUnsafePointer !
として導入されているが、これは確かに可能である.Swiftがインポートしたstrlen関数の完全なタイプは次のように定義されます.
func strlen(_ __s: UnsafePointer!) -> UInt
タイプインスペクタは、String値を
UnsafePointer
またはUnsafePointer
パラメータに渡すことができる.この過程で、コンパイラは、UTF-8で1を符号化し、null
で終了した文字列を含み、バッファを指すポインタを関数に返すバッファを暗黙的に作成した.C文字列配列の組み込みサポートはありません
Swiftが単一の
char *
パラメータを処理する方法は非常に簡便である.しかし、いくつかのC関数は、パラメータとして文字列配列(char *
またはchar * []
)を受信するが、Swiftペアは、[String]
をchar *
パラメータに渡すのに内蔵されていない.実用的な例は、サブプロセス起動時のposix_です.spawn関数.posix_spawnの最後の2つのパラメータ(
argv
およびenvp
)は、新しいプロセスのパラメータおよび環境変数を伝達するための文字列配列である.ドキュメントでは、次のように説明されています.argv
(およびenvp
)は、null
で終わる文字列配列ポインタを指し、配列要素はnull
で終わる文字列を指す.Swiftは、これらのパラメータのうちCタイプの
char *
const
argv []
を処理しにくいUnsafePointer
に変換し、感嘆符はオプション値に対して暗黙的にパケットを解くことを示し、ここでAPIのパラメータが空にならないことを示している.すなわち、Swiftは関数がNULLの伝達を受け入れるかどうか分からない(この場合、外層?>!
はオプション値になる).私たちはドキュメントを参考にしてこの質問に答えなければなりません.この例では、文書は、UnsafePointer
が少なくとも1つの要素(生成プログラムのファイル名)を含む必要があることを明確に宣言している.argv
は、親プロセスの環境を継承することを示すenvp
であってもよい.Swift文字列配列をC文字列配列に変換
posix_spawn 2は優雅なSwiftインタフェースを提供します.カプセル化関数は、起動中のプログラムのパスと文字列配列のパラメータを受信する必要があります.
///
///
/// - Returns: A pair containing the return value of `posix_spawn` and the pid of the spawned process.
func spawn(path: String, arguments: [String]) -> Int32
パラメータ配列をposix_に変換する必要がありますspawnが受信できるフォーマット.これにはいくつかのステップが必要です.
NULL
標準ライブラリSwiftチームもこの機能を使用して標準ライブラリのユニットテストを実行する必要があるため、標準ライブラリのソースには
withArrayOfCStrings
という関数も含まれています.現在、これはプライベート関数であり、stdlibユーザーに公開されていません(withArrayOfCStrings
と宣言されていますが、そうしないとユニットテストでは見えません).しかし、この関数は依然として私たちに見えます.これは関数のインタフェースです.
public func withArrayOfCStrings(
_ args: [String],
_ body: ([UnsafeMutablePointer?]) -> R
) -> R
それはwithUnsafePointerとその変体と同じ形式を持っている:その結果タイプRは汎用であり、パラメータとして閉パケットを受信する.文字列配列をC配列に変換した後、
public
は閉パケットを呼び出し、C配列を伝達し、閉パケットの戻り値を呼び出して呼び出し者に転送することを考えている.これにより、withArrayOfCStrings
関数は、自身がバッファを作成するライフサイクルを完全に制御します.withArrayOfCStrings
関数を実現することができます.
/// 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)
}
}
なぜこれが可能なのでしょうか.
spawn
の閉包パラメータのタイプはwithArrayOfCStrings
であることに留意されたい.パラメータタイプ([UnsafeMutablePointer?]) -> R
は、[UnsafeMutablePointer ?]
によって要求されるposix_spawn
と互換性がないように見えるが、実際には互換性がある.UnsafePointer ?>!
はCChar
の別名にすぎません.さらに、コンパイラは、SwiftがCに伝達する文字列に対して特別な処理を行うように、Int8
パラメータを受信するC関数に、生のSwift配列を暗黙的に伝達する.したがって、配列をUnsafePointer
に直接渡すことができ、その要素タイプがポインタが要素を指すタイプと一致する限り.posix_spawn
関数を使用した例です.
let (retval, pid) = spawn(path: "/bin/ls", arguments: ["-l", "-a"])
これは実行プログラムの出力です.
$ 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で呼び出すと、
spawn
にエラーが返されます.playgroundの砂箱ではサブプロセスの生成が許可されていない可能性があります.そのため、コマンドラインで作成するか、Xcodeで新しいコマンド項目を作成することが望ましいです).さぎょうげんり
posix_spawn
の完全な実装は以下の通りである.
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)
}
}
逐行解説しましょう.第1の動作で入力された文字列は、UTF-8によって符号化された文字数(空の終端識別子の1バイトを加えた)の配列を作成します.
let argsCounts = Array(args.map { $0.utf8.count + 1 })
次の行では、これらの文字数を読み取り、入力文字列ごとの文字オフセットを計算します.すなわち、各文字列がバッファ内の開始位置になるようにします.最初の文字列はもちろん、オフセット量がゼロの場所に配置され、文字数を累積することで後続のオフセット量を計算します.
let argsOffsets = [ 0 ] + scan(argsCounts, 0, +)
コードはscanというヘルプ関数を使用し、同じファイルに定義されます.
withArrayOfCStrings
に含まれる要素の数は、argsOffsets
よりも多いことに留意されたい.argsCounts
の最後の要素は、最後の入力文字列の後のオフセット量、すなわち必要なバッファのサイズであるからである.次に、バッファとしてバイト配列(要素タイプ
argsOffsets
)を作成します.バッファは自動的に増加するため、UInt8
を呼び出す必要はありません.しかし、開始時に必要な容量を事前に知っておくことができれば、重複した割り当て動作を回避することができます.
let argsBufferSize = argsOffsets.last!
var argsBuffer: [UInt8] = []
argsBuffer.reserveCapacity(argsBufferSize)
reserveCapacity
でエンコードされたバイトをバッファに書き込み、入力された文字列ごとに空のバイトを追加できます.
for arg in args {
argsBuffer.append(contentsOf: arg.utf8)
argsBuffer.append(0)
}
このとき、正しいフォーマットのバイト配列(
UTF-8
)があります.バッファ内の要素を指すポインタ配列を構築する必要があります.これが関数の最後の部分の役割です.
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を用いて配列を取得し,その要素はバッファへのポインタを表す.内部閉パケットの第1行コードは、
UInt8
によって要素ポインタのタイプをUnsafeMutableRawPointer
からUnsafeMutablePointer
に変換する.(Swift 3.0から、タイプ化されたポインタの間で直接変換することはできません.まずUnsafeMutablePointer
に変換する必要があります.)このコードの可読性はよくありませんが、私たちにとってこの行の後の内容が重要です.ローカルUnsafe[Mutable] RawPointer
変数は、バッファ内の最初のバイトを指すptr
である.次に、ポインタ配列を構築するために、2行目に作成した文字オフセット配列をマッピングし、各オフセット量に基づいてポインタを後方に移動します.最後に、結果配列の最後の要素を
UnsafeMutablePointer
に設定し、配列の最後を表す空のポインタとして使用します(入力配列よりもnil
の方が多くの要素を含むことを覚えていますか?したがって、最後の要素を書き換えるのは正しいです).最後に,呼び出し者から渡された閉パケットを呼び出し,C文字列を指すポインタ配列を渡すことができる.
本文はSwiftGG翻訳グループから翻訳して、すでに作者の翻訳の授権を得て、最新の文章は訪問して下さいhttp://swift.gg.
argsOffset
形式で伝達されるため、UTF-8
関数に4つの「文字」が占有されることに注意してください.↩ strlen
を用いて説明する.しかし、生産コードでは、posix_spawn
フレームワーク内のより高度なFoundation
クラス(née Process
)を使用して実現されるべきである.↩