C
Swift

Swift - UnsafePointerと上手に付き合う

More than 3 years have passed since last update.

ああなんてもったいない誤解。

ということでUnsafePointer.alloc()でアンセーフなメモリを確保して、その中に返してもらって、利用後に速やかに破棄するという感じにしかならないようです。 - SwiftからMachのhost_info()を呼び出す(UnsafePointerのキャスト)

Cの構造体はそのままSwiftの構造体として使える

  • Cの構造体も、Swiftの構造体と同じ流儀で扱えます
  • UnsafePointer<cstruct>を要求する関数には、&cstructを渡すだけでおk

実際にご覧いただきましょう。

import Darwin // もちろんFoundationでもおk
func lastModified(path:String)->String? {
    var st = stat()    // *1
    let err = path.withCString { // *2
        lstat($0, &st) // *3
    }
    if err != 0 {
        path.withCString { perror($0) }
        return nil
    }
    let ct = ctime(&st.st_mtimespec.tv_sec) // *4
    return String.fromCString(ct)
}
println(lastModified("/etc/csh.cshrc"))    // 成功例
println(lastModified("/etc/csh.profile"))  // 失敗例

では解説。

  1. stはCのstruct statにゼロ初期化
  2. .withCStringを使うと、引数ブロックの第一引数がUnsafePointer<CChar>になるので、C文字列を要求する関数はこの方法で使える
  3. stの内容を設定
  4. 構造体内の構造体もSwiftの流儀でアクセスできる

可変長バッファーには、SwiftのArrayが使える

import Darwin
let linebufsize = 4096
func printFileWithLineNumber(path:String) {
    let fp = path.withCString {
        fopen($0, "r") // *1
    }
    if fp == nil { return }
    var linebuf = [CChar](count:linebufsize, repeatedValue:0) // *2
    var lineCount = 0
    //          *3
    while fgets(&linebuf, Int32(linebufsize-1), fp) { 
        if let line = String.fromCString(&linebuf) {
            print("\(++lineCount):\t" + line)
        }
    }
    fclose(fp)
}
printFileWithLineNumber("/etc/csh.cshrc")
  1. リテラルを直接C関数に書くことで、"r"UnsafePointer<Int8>型推論される
  2. バッファーlinebufはこうして確保してから
  3. &linebufをC関数に渡せばおk

追記: 初期化子cstruct()が未定義の場合

コメント欄で気づいたのですが、host_basic_infoはゼロ初期化子が未定義なのですね。しかし、そういう場合もこう出来ます。

func blankof<T>(type:T.Type) -> T {
    var ptr = UnsafeMutablePointer<T>.alloc(sizeof(T))
    var val = ptr.memory
    ptr.destroy()
    return val
}

このblankof(T)はTがどんな値型でもゼロ初期化できるすぐれもの。.destroy()忘れもありえません。

これを使ってrefactorしてみました。

import Foundation
func getHostBasicInfo()->host_basic_info? {
    /// カーネル用のポートを取得する
    let hostPort = mach_host_self()
    /// ゼロ初期化済みのhost_basic_infoを用意
    var hostBasicInfo = blankof(host_basic_info)
    /// HOST_BASIC_INFO_COUNTが定義されていないので自分で計算する
    var count = mach_msg_type_number_t(
        sizeof(host_basic_info) / sizeof(integer_t)
    )
    /// host_info()を呼び出して値を取得する
    var err = withUnsafePointer(&hostBasicInfo) {
        host_info(hostPort, HOST_BASIC_INFO, host_info_t($0), &count)
    }
    /// 問題なければhostBasicInfoを、あったらnilを返す
    return err == 0 ? hostBasicInfo : nil
}

追記: C構造体にだってextensible!

Swiftがイカすのは、importしたCの構造体すらextensionの対象となること。なのでこうしておけば…

extension host_basic_info: Printable {
    public var description:String {
        return "\n".join(["host_basic_info(",
            "  mac_cpus:         \(self.max_cpus),",
            "  avail_cpus:       \(self.avail_cpus),",
            "  memory_size:      \(self.memory_size),",
            "  cpu_type:         \(self.cpu_type),",
            "  cpu_subtype:      \(self.cpu_subtype),",
            "  cpu_threadtype:   \(self.cpu_threadtype),",
            "  physical_cpu:     \(self.physical_cpu),",
            "  physical_cpu_max: \(self.physical_cpu_max),",
            "  logical_cpu:      \(self.logical_cpu),",
            "  logical_cpu_max:  \(self.logical_cpu_max),",
            "  max_mem:          \(self.max_mem)",
        ")"])
    }
}

あとはこれだけで、中身を確認できます。

println(getHostBasicInfo())

まとめ

みてのとおり、どちらのコードサンプルにも、blankof()を除いてUnsafePointer<toWhatever>な型宣言は一つもありません。なのにCの構造体も関数も自在に使いこなせています。

実はこのあたりのことはAppleのInteracting with C APIsにもきちんと書かれています。が、コードサンプルが抽象的でいまいちわかりづらかった。上記みたいなサンプルを作ってみると手になじんできます。

私にとって高レベル側のSwift最大の魅力が演算子なら、Cインターフェイスは低レベル側最大の魅力です。Perl5のXSでヒャッハーしてたころが懐かしい…

Enjoy!

Dan the Multilingual