ああなんてもったいない誤解。
ということで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")) // 失敗例
では解説。
-
st
はCのstruct stat
にゼロ初期化 -
.withCString
を使うと、引数ブロックの第一引数がUnsafePointer<CChar>
になるので、C文字列を要求する関数はこの方法で使える -
st
の内容を設定 - 構造体内の構造体も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")
- リテラルを直接C関数に書くことで、
"r"
がUnsafePointer<Int8>
型推論される - バッファー
linebuf
はこうして確保してから -
&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