文字列配列をSwiftからCに渡す方法

8285 ワード

作者:Natasha The Robot,原文リンク,原文日付:2016-10-27訳者:BigbigChai;校正:walkingway;原稿:CMB
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が受信できるフォーマット.これにはいくつかのステップが必要です.
  • は、文字列要素をUTF-8で符号化する.
  • は、各UTF-8によって符号化された文字列の末尾に空のバイトを追加する.
  • は、すべてのUTF-8符号化された、空のバイトで終わる文字列を1つのバッファにコピーする.
  • バッファの最後に、C配列の最後を示す別の空のバイトを追加します.
  • バッファが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.
  • 上のemojiはargsOffset形式で伝達されるため、UTF-8関数に4つの「文字」が占有されることに注意してください.↩
  • ここでは簡単な例としてstrlenを用いて説明する.しかし、生産コードでは、posix_spawnフレームワーク内のより高度なFoundationクラス(née Process)を使用して実現されるべきである.↩