SwiftでもCのシステムコールを呼ぶことができるのでうまく使えば、UNIXドメインソケットを使ったSwiftとCのライブラリ間のスレッド通信を行うことができる。
Cでの操作方法
C言語でUNIXドメインソケットを使う場合にはsockaddr_un構造体をsockaddrにキャストしてbindやconnectに渡す必要がある。
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
strcpy(addr.sun_path, "/tmp/hogehoge");
addr.sun_family = AF_UNIX;
bind(sock, (struct sockaddr *)&addr, sizeof(addr));
Swiftでの操作方法
Swiftで同様にキャストを行なう必要があるが、let sockAddrUn = sockAddr as sockaddr_un
のようなキャストはできない。
Swiftの場合は、
sockaddr_un構造体 → UnsafeRawPointer化 → assumingMemoryBoundを使ってsockaddr構造体に変換
のようなステップを踏む必要がある。
具体的には、次のように書く
let sock = socket(AF_UNIX, SOCK_STREAM, 0)
let sockPath = "/tmp/hogehoge"
var sockAddrUn = sockaddr_un()
sockAddrUn.sun_family = sa_family_t(AF_UNIX)
sockPath.withCString { path in
Darwin.memcpy(&(sockAddrUn.sun_path), path, Int(strlen(path)))
}
let rawPtr = UnsafeRawPointer(&sockAddrUn)
let sockAddrPtr = rawPtr.assumingMemoryBound(to: sockaddr.self)
Darwin.bind(sock, sockAddrPtr, socklen_t(MemoryLayout<sockaddr_un>.stride))
こんな書き方は絶対ダメ
因みに最初に構造体のキャスト方法がわからず変数を二つ用意してmemcpyをするという荒技を行なっていた。
let sock = socket(AF_UNIX, SOCK_STREAM, 0)
let sockPath = "/tmp/hogehoge"
var sockAddrUn = sockaddr_un()
sockAddrUn.sun_family = sa_family_t(AF_UNIX)
sockPath.withCString { path in
Darwin.memcpy(&(sockAddrUn.sun_path), path, Int(strlen(path)))
}
var sockAddr = sockaddr()
Darwin.memcpy(&sockAddr, &sockAddrUn, MemoryLayout<sockaddr_un>.stride)
Darwin.bind(sock, &sockAddr, socklen_t(MemoryLayout<sockaddr_un>.stride))
sockaddr_unの分だけコピーしてbindにsockaddr_unの長さを渡すのでビルドは通るし、動きはするがReleaseビルドで最適化設定がされるとsockaddrからはみ出た分のデータが消えてしまい、bindがEAFNOSUPPORTエラーを返すようになる。
A構造体 → UnsafeRawPointer → assumingMemoryBoundでB構造体 でキャストするようにしましょう。