LoginSignup
16
15

More than 5 years have passed since last update.

libuv を使ったEcho Server を移植するのをネタにSwift / Cブリッジについて入門する

Posted at

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 を追加します。
LibrarySetting.png
SearchPath.png

libuv が呼び出せるようにする

Bridging-Header.h を何らかの方法で作り(Objective-C のファイルを新規作成して削除する方法が楽)、

#include <uv.h>

を追加します。

すると下の図のようにlibuv のメソッドがサジェストされるようになります。
suggest.png

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 上でサジェストされる型は Int8Int32 のようにビット数が明示された型になっています。)

また、ポインタについては Type *UnsafeMutablePointer<Type> に、 const Type *UnsafePointer<Type> になります。また、NULLnil に変わります。

通常の型の変換とポインタの型の変換を踏まえると

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 でサーバープログラムなどが実装されてくるとより面白くなってくると思います。

この記事はいろいろな方々の記事の寄せ集めになってしまいましたが、同じようなことにチャレンジする方が増えてくだされば幸いです。

参考文献

16
15
0

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
16
15