LoginSignup
98
92

More than 5 years have passed since last update.

Swift - UnsafePointerと上手に付き合う

Last updated at Posted at 2014-07-27

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

ということで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

98
92
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
98
92