Edited at

Swift - UnsafePointerと上手に付き合う

More than 5 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