Swiftコード内から自作C関数を利用するには
Xcode 6.0 Beta
前回は、Swiftコードから、「C++をラップしたObjective-Cクラス」を利用する方法を調査した。結果、うまくいった。
今回は引き続き、SwiftからCの関数を呼び出すやり方を調べてみた。
Cは、C++と比べると、Swiftとの親和性が高い。ブリッジングが必要なことは同じだが、ブリッジングさえすれば、Swift内から簡単にCの関数を呼び出すことができる。Objective-Cでラップする必要がないという点で、パフォーメンス面で少し有利であろう。しかし、実際にやってみると、ハマリポイントがかなり多かった。Swiftのデータ型をきちんと理解していかないと、データの受け渡し部分で混乱してしまう。私はおおいに迷い、彷徨って、試行錯誤したことの大半は徒労に終わった。これから同じことをやろうとする人には少しでも楽をしてもらいたいものだ。だがしかし、そのためにこの記事が役に立つかどうかといえば、不明。
では、順を追って進めてみよう。
まず、今ここに、既に完成されたCの関数群があったとする。
以下は線形リストを扱うAPIであるが、本件においては、Swiftとの連動ができれば良いのであって、実用性は重要ではない。まあ、内部構造を隠蔽したポインタの受け渡しが多いので、検証材料としては良いかと思う。
【test.h】
#pragma once #include <stddef.h> #include <stdbool.h> typedef struct { void* next; void* data; } LinkedListElement; typedef struct { LinkedListElement* root; LinkedListElement* tail; size_t length; } LinkedList; typedef struct { LinkedList* p; LinkedListElement* next; } LinkedListIterator; LinkedList* LinkedListCreate(); size_t LinkedListGetLength(LinkedList* p); int LinkedListAttachElement(LinkedList* p, void* element); int LinkedListAddElement(LinkedList* p, void* element, size_t size); LinkedListIterator LinkedListGetIterator(LinkedList* p); bool LinkedListHasNext(LinkedListIterator* itr); void* LinkedListNextElement(LinkedListIterator* itr); void LinkedListDetachAllElements(LinkedList* p); void LinkedListRelease(LinkedList* p);
【test.c】
#include "test.h" #include <stdlib.h> #include <string.h> LinkedList* LinkedListCreate() { LinkedList* p = malloc(sizeof(LinkedList)); p->root = p->tail = NULL; p->length = 0; return p; } inline size_t LinkedListGetLength(LinkedList* p) { return p->length; } int LinkedListAttachElement(LinkedList* p, void* element) { LinkedListElement* e = malloc(sizeof(LinkedListElement)); if (e) { e->data = element; e->next = NULL; if (!p->root || !p->tail) { p->root = e; } else { p->tail->next = e; } p->tail = e; p->length = p->length + 1; return 1; } else { return -1; } } int LinkedListAddElement(LinkedList* p, void* element, size_t size) { void* data = malloc(size); if (data) { memcpy(data, element, size); int result = LinkedListAttachElement(p, data); if (0 < result) { return result; } else { free(data); return -1; } } else { return -1; } } LinkedListIterator LinkedListGetIterator(LinkedList* p) { LinkedListIterator itr; itr.p = p; itr.next = p->root; return itr; } inline bool LinkedListHasNext(LinkedListIterator* itr) { return NULL != itr->next; } void* LinkedListNextElement(LinkedListIterator* itr) { LinkedListElement* e = itr->next; itr->next = e->next; return e->data; } void LinkedListDetachAllElements(LinkedList* p) { // detach処理 // エレメント内のデータは解放せずに所有権を放棄する LinkedListElement* e = p->root; while (e) { LinkedListElement* c = e; e = e->next; free(c); } p->root = NULL; p->tail = NULL; p->length = 0; } void LinkedListRelease(LinkedList* p) { if (!p) { return; } LinkedListElement* e = p->root; while (e) { LinkedListElement* c = e; e = e->next; free(c->data); free(c); } free(p); }
サンプルとしては決してミニマルではないが、その分、後でポイントとなる部分を多く解説する。ここまではただのCの実装なので、特に問題はないだろう。
Cの関数をSwift内から使うためには、ブリッジングヘッダ内で必要なヘッダファイルのimportを行う。
プロジェクトに初めて .c や .m などのファイルを追加するときに、ブリッジングヘッダを作るかどうかXcodeに聞かれるので、Yesと答えれば自動的に作成してくれる。自分で作る場合でも、名前さえ合っていれば大丈夫なはず。
初期状態ではコメントが書かれているだけなので、必要なヘッダ(Swift側から使いたい関数のプロトタイプ宣言や構造体などが定義されたヘッダ)のimport文を自分で加えてやること。
【ProjectName-Bridging-Header】
// // Use this file to import your target's public headers that you would like to expose to Swift. // #import "test.h"
これで準備OK
.swift内からCのヘッダなどをimportする必要はない。
以下にSwiftからCの関数を呼び出す試験コードを掲載する。
【main.swift】
(コンソールアプリ)
import Foundation // SwiftのStringオブジェクトの配列を用意 let table = ["ABC", "DEF", "GHI", "JKL"] // Cの自作関数を呼んでみる // 呼び出し先の内部でメモリをアロケートして、戻り値でポインタ変数を取得しているが、特別な書き方は要らない // Swift関数と同じ呼び出し形式でOK let list = LinkedListCreate() // Swiftの文字列配列をイテレーション for s in table { // SwiftのStringオブジェクトをCChar配列に変換 var array: CChar[] = s.cStringUsingEncoding(NSUTF8StringEncoding)! // 文字列のメモリサイズを計算 let size: UInt = UInt(sizeof(CChar) * array.count) // CのAPI呼び出し、内部的にメモリをコピーしている // 引数のデータ型、渡し方に注意 LinkedListAddElement(list, &array, size) } // ... // Cの関数を使って文字列をイテレーション // itrはポインタで返しているわけではないが、ポインタ変数のときと同じ受け取り方でOK // 変数のアドレスを渡すときの書式はCと同じで、変数名の前にアンパサンド for var itr = LinkedListGetIterator(list); LinkedListHasNext(&itr); { // Cのvoid*型変数を取得 let c: COpaquePointer = LinkedListNextElement(&itr) // 変数cを、「C言語のchar型のポインタ」としてのSwiftオブジェクトでラップ(new)し、 // SwiftのStringクラスのStatic Factoryメソッド fromCString の引数に渡すことによって、 // SwiftのStringオブジェクトを生成。この辺ちょっとややこしい。 let s: String = String.fromCString(UnsafePointer<CChar>(c)) // 確認のための出力 // "ABC", "DEF", "GHI", "JKL"の順に出力される println(s) } // メモリ解放 LinkedListRelease(list)
以上で実装完了。「どっかで落ちるんだろうな〜」と内心思いながら、ぷるぷるする手で実行ボタンを押したところ、きちんとコンパイルも通り、正常に実行できた。というか、本当のところは、ここにたどり着くまでに、何度もコンパイルエラーやら実行時におかしくなるのを目にした。最終的に、ようやく動いた、という方が正しい。
Swiftでは型推論が働くので、定数、及び変数の宣言時に型の指定は不要であるが、上記サンプルでは、実際にどのような値を受け渡ししているのか分かりやすいように、型を明示的に指定している。
実際のところ、Swiftに十分に慣れるまでは、型を指定した方が良いと思う。
たとえば、SwiftのStringと、Objective-CのNSStringの間には互換性があり、
var s1: String = "A"
と書くこともできるし、
var s2: NSString = "B"
と書くこともできるし、
s1 = s2
とすることもできてしまう。
ところがどっこい、それぞれをC文字列に変換しようとした場合、
let c1 = s1.bridgeToObjectiveC().UTF8String let c2 = s2.UTF8String
といった感じで、SwiftのStringがNSStringの機能をそのまま使えるわけではなく、一度変換が必要になる。
ややこしい。
ちなみに、上記のc1, c2は型推論を使っているので、これも慣れないと何をしているのかわからない。
c1, c2は両方ともCString型定数である。
StringとNSStringの間は互換性があるが、StringとCStringには直接の互換性はない。
NSStringとCStringの間にも直接の互換性はない。
CStringは、const char*との互換性がある。
ここで言っている互換性とは、引数としてそのまま渡せるかどうかである。
整理すると、
// Swift関数 func f1(s: String) {} func f2(s: NSString) {}
// ブリッジングされているCの関数 void f3(const char* str);
// Swiftコード var s1: String = "A" var s2: NSString = "B" var s3: CString = "C" f1(s1) // OK f1(s2) // OK f1(s3) // NG f2(s1) // OK f2(s2) // OK f2(s3) // NG f3(s1) // NG f3(s2) // NG f3(s3) // OK
ということである。
要するに、今回のことから得るべき教訓は、
・SwiftとObjective-C、Cとの間の型変換に気をつけましょうということと、
・ある程度慣れるまでは型推論させずに、明示的に型指定した方がいいかもよ
ということであった。
特に、Cとやり取りしているポインタ型に何を使っているのか、何を使ったらいいのかではまり出すと地獄になる。たとえば、先のサンプルでは、String → CChar[] → CMutableVoidPointer → void* ということをさらっとやっているように見えるだろうが(見えないかもしれないが)、実はコレ、String → CString → void* でやろうとした場合、私にはどうやってもできなかった。(追記:すぐ翌日にそれはできた。やり方は次の記事で。)
そのあたりについて、main.swfitとして掲載したコード例の内部をもう少し補足説明したいところだが、今回は長くなってきたのでここで終わる。