Swift から C言語のライブラリを直接扱えることが出来るのは知っていたのですが、今までやったことがなかったため libuv のチュートリアルである uvbook に載っている tcp-echo-server をSwift に移植して、C言語ブリッジについて入門してみたいと思います。
環境構築
今回はMac上でXcodeを使いtcp-echo-server を移植することにします。
ソースコードはここにあります https://github.com/yokochie/EchoServer-Sample
Xcodeのバージョンは7.2、Swiftのバージョンは2.1.1になります。
libuv のインストール
libuv の tcp-echo-server を移植するため libuv が必要です。インストールは Homebrew と使えば簡単にできます。
$ brew install --HEAD libuv
インストールすると /usr/local/{include,lib} 以下に必要なファイルが入ります。
$ ls /usr/local/include/uv*.h
/usr/local/include/uv-darwin.h /usr/local/include/uv-threadpool.h /usr/local/include/uv-version.h
/usr/local/include/uv-errno.h /usr/local/include/uv-unix.h /usr/local/include/uv.h
$ ls /usr/local/lib/libuv*
/usr/local/lib/libuv.1.dylib /usr/local/lib/libuv.a /usr/local/lib/libuv.dylib
Xcode プロジェクトの作成
SwiftとLibpcapによるパケットモニターの作成 | Developers.IO の記事を参考に Command Line Toolのプロジェクトを作ります。
libuv を使うため、まずはプロジェクトの設定から libuv.dylib の追加と Search Path の Header Search Path, Library Search Path に /usr/local/include/, /usr/local/lib を追加します。
libuv が呼び出せるようにする
Bridging-Header.h を何らかの方法で作り(Objective-C のファイルを新規作成して削除する方法が楽)、
#include <uv.h>
を追加します。
すると下の図のようにlibuv のメソッドがサジェストされるようになります。
Swift/C ブリッジ入門
https://github.com/yokochie/EchoServer-Sample/blob/master/EchoServer/main.swift にあえてC言語での実装( https://github.com/nikhilm/uvbook/blob/master/code/tcp-echo-server/main.c )と似せて書いたものを載せました。環境構築の最後に載せた図のように関数はそのまま呼べるため、変数の型をちゃんと合わせてしまえばすんなり移植できました。この記事を読みつつ見比べてみると理解しやすいかと思います。
変数の型について
Using Swift with Cocoa and Objective-C (Swift 2.1): Interacting with C APIsに載っているように CChar
, CInt
のような頭文字Cで始まる型に変換されます。(ただし、XCode 上でサジェストされる型は Int8
や Int32
のようにビット数が明示された型になっています。)
また、ポインタについては Type *
が UnsafeMutablePointer<Type>
に、 const Type *
が UnsafePointer<Type>
になります。また、NULL
は nil
に変わります。
通常の型の変換とポインタの型の変換を踏まえると
int * hoge = NULL
が
var hoge: UnsafeMutablePointer<CInt> = nil
になります。
ポインタの扱いについて
Cの構造体はそのままSwiftの構造体として使えるにも書いてありますが、変数のポインタを要求する関数である場合は &
を頭につければOKです(メソッドの定義に inout をつけたものと同じ扱い)。今回のソースコードだと main.swift:66 で行なっています。
uv_ip4_addr("0.0.0.0", CInt(DEFAULT_PORT), &addr)
ただし、この手法はキャストが発生する場合はダメなようです。
https://github.com/nikhilm/uvbook/blob/master/code/tcp-echo-server/main.c#L64 の
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
をそのまま、&
を付けて移植は型が合わずコンパイルエラーとなりました。
そのため、withUnsafePointer
関数と UnsafePointer
のイニシャライザを使ってキャストを行なっています。
withUnsafePointer(&addr) {
uv_tcp_bind(&server, UnsafePointer<sockaddr>($0), 0)
}
(UnsafePointer Structure Referenceの Initializers にポインタ内部の変数の型を直接取れるものがない点にご注意ください)
また、libuv ではコールバック関数を多用するのですが、グローバルで宣言された関数についてはそのまま指定することが出来ました。
例えば、uv_listen
関数では引数の最後に void (*uv_connection_cb)(uv_stream_t* server, int status)
を取ります。Swift で実装された https://github.com/yokochie/EchoServer-Sample/blob/master/EchoServer/main.swift#L73 では func on_new_connect(server: UnsafeMutablePointer<uv_stream_t>, status: CInt)
をそのまま受け渡すことができています。
ポインタ内部の扱い
C言語では *
演算子や ->
演算子を使ってポインタ内の変数を参照できましたが、Swift では memory
プロパティを使います(https://github.com/yokochie/EchoServer-Sample/blob/master/EchoServer/main.swift#L13 で使っています)
メモリの確保は UnsafeMutablePointer<CChar>.alloc(size)
、解放は destory
メソッドを使用します。
その他
Swift Standard Library に標準エラー出力に出力するといった関数がないようですので今回は標準出力にエラー文を出しています。
http://ericasadun.com/2015/06/09/swift-2-0-how-to-print/ に書いてあるように OutputStream を開いて出力すればできるようです。
実装確認
Xcode 上で ⌘R を押し実行します。その後、Terminal で
$ telnet localhost 7000
を実行します。 hoge
といった何らかの文字を入力し、同じ文字列が表示されたらOKです。
実行確認例
$ telnet localhost 7000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hoge
hoge
コマンドラインからビルドしたい場合は
$ swiftc main.swift -import-objc-header ../EchoServer-Bridging-Header.h -I/usr/local/include -L/usr/local/lib -luv
のようにすれば、同じディレクトリに main
ファイルが増えているためそれを実行すればOKです。
グローバル関数の代わりにクロージャを使用
Swift2で作るコマンドラインツール - クックパッド開発者ブログの「signalをフックする」の項に書いてあるようにクロージャを関数ポインタとして渡すことが可能です。
https://github.com/yokochie/EchoServer-Sample/blob/use_closure/EchoServer/main.swift にクロージャに変えて実装したものを用意しました。
ほとんど変わらないと思いますが、通常のクロージャ型の前に @convention(c)
が付いています。(以下、一例)
let alloc_buffer: @convention(c) (UnsafeMutablePointer<uv_handle_t>, size_t, UnsafeMutablePointer<uv_buf_t>) -> Void = { (handle, suggested_size, buf) in
buf.memory.base = UnsafeMutablePointer<CChar>.alloc(suggested_size)
buf.memory.len = suggested_size
}
終わりに(感想)
今回初めて Swift から C言語のライブラリを触りましたが、思いのほか簡単に扱え驚きました。ソースコードを見比べると 変数、関数の定義の仕方の違いはありますが、よく似ていると思います。
iOS アプリ実装言語としてSwift が注目されていると思いますが、Swift でサーバープログラムなどが実装されてくるとより面白くなってくると思います。
この記事はいろいろな方々の記事の寄せ集めになってしまいましたが、同じようなことにチャレンジする方が増えてくだされば幸いです。