SwiftからC言語の関数を直接使うためには、Swiftでメモリバッファを考慮したコードを書く必要があります。Unsafe系のタイプを使う必要があり、分かりづらいコードが多くなります。
この記事ではコード例を紹介します。
String から Cスタイル文字列を渡す
まずは、HelloWorld の例からです。但し、Swift の print
関数ではなく、 C の printf
を使っています。
次のようなCのコードがあるとします。引数で渡されたC文字列をprintf
で出力します。SwiftはCの可変長引数関数には対応していないので、say
というラッパー関数を作りました。
void say(const char *str)
{
printf("%s\n", str);
}
Swift側のコードは次の通りです。ポイントはSwiftのString
からC文字列を取得するために、cString(using:)
関数を使っています。
import Foundation
let str = "Hello, World!"
say(str.cString(using: .utf8))
C側で文字列を返す
逆に、C側で文字列を書き込めるように、Swiftからはバッファを渡す場合です。
C側の関数は次のように渡されたバッファに文字列を書き込む関数があるとします。
void expression(int i, int j, char *result)
{
int k = i + j;
sprintf(result, "%d + %d = %d", i, j, k);
}
Swift側のコードは次のようになります。
var buf = [CChar](repeating: 0, count: 256)
expression(1, 2, &buf)
if let str = String(cString: buf, encoding: .utf8) {
print(str)
}
Cのint
には、SwiftのInt
を直接渡すことができます。char *
のバッファは、CChar
の配列を作り、「&
」演算子を使います。「&
」演算子を使うことで UnsafeMutablePointer<CChar>
を取得できます。buf
には、Cスタイルの文字列が書き込まれているので、String(cString:encoding:)
イニシャライザを使って、String
を作ります。返されるString
はオプショナルなので、if let
で通常の変数に変換しています。
スカラーのポインタ渡し
スカラーのポインタを渡すときは「&
」演算子を使います。
次のようなCのコードがあるとします。
void say2(const int *ip)
{
printf("%d\n", *ip);
}
Swift側のコードは次のようになります。
var i: Int32 = 10
say2(&i)
C側で値を書き込むときも同じです。次のようなCのコードがあるとします。
void calc(int i, int j, int *result)
{
*result = i * j;
}
Swift側のコードは次のようになります。
var result: Int32 = 0
calc(10, 20, &result)
print(result)
構造体のポインタ渡し
C側で定義された構造体はSwiftの構造体に自動的にマッピングされます。
Cの関数は引数に構造体へのポインタを渡すケースが多々あります。
次のようなCのコードがあるとします。
typedef struct POINT {
double x;
double y;
} POINT;
void movePoint(POINT *point, double dx, double dy)
{
point->x += dx;
point->y += dy;
}
この関数を利用するSwift側のコードは次のようになります。
var pt = POINT(x: 0, y: 0)
movePoint(&pt, -10, 20)
print("x=\(pt.x), y=\(pt.y)")
配列を渡す
C側の関数が配列を引数に取るときのコード例です。次のようなCのコードがあるとします。
void printArray(const int *array, int count)
{
for (int i = 0; i < count; i++)
{
printf("[%d]: %d\n", i, array[i]);
}
}
Swift側のコードは次のようになります。スカラーや構造体と同じように、「&
」演算子を使用します。
var values: [Int32] = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
printArray(&values, Int32(values.count))
C側で書き込むときも同じです。例えば次のようなCの関数があるとします。
void fillArray(int *array, int count)
{
for (int i = 0; i < count; i++)
{
array[i] = i;
}
}
これを呼び出すSwift側のコードは次のようになります。Swift側で値を0にした配列を作って渡します。
var values = [Int32](repeating: 0, count: 10)
fillArray(&values, Int32(values.count))
print(values)
ヒープを直に使う
malloc
で確保したバッファを使う必要があるときは、Swiftからもmalloc
を使うことができます。例えば、次のようなCのコードがあるとします。
typedef struct POINT {
double x;
double y;
} POINT;
typedef struct SHAPE {
POINT *pointList;
int numOfPoints;
} SHAPE;
void printShape(const SHAPE *shape)
{
printf("[");
for (int i = 0; i < shape->numOfPoints; i++)
{
if (i + 1 < shape->numOfPoints)
{
printf("(%g,%g), ", shape->pointList[i].x, shape->pointList[i].y);
}
else
{
printf("(%g,%g)", shape->pointList[i].x, shape->pointList[i].y);
}
}
printf("]\n");
}
void freeShape(SHAPE *shape)
{
free(shape->pointList);
}
freeShape
関数はfree
関数でバッファを解放するので、malloc
関数でバッファを確保する必要があります。Swift側のコードは次のようになります。
var shape = SHAPE()
var buf = malloc(MemoryLayout<POINT>.size * 3)
if buf != nil {
if let pointList = buf?.bindMemory(to: POINT.self, capacity: 3) {
let points: [POINT] = [POINT(x: 10, y: 1),
POINT(x: 20, y: 2),
POINT(x: 30, y: 3)]
for (i, pt) in points.enumerated() {
pointList[i] = pt
}
shape.pointList = pointList
shape.numOfPoints = 3
}
}
printShape(&shape)
freeShape(&shape)
ポイントはmalloc
関数を使うことですが、そのためにはSHAPE
構造体のバイト長を調べる必要があります。Cではsizeof()
演算子を使用しますが、Swiftの場合はこのようにMemoryLayout
を使用します。また、返されたバッファはUnsafeMutableRawPointer
なので、bind
を使って、UnsafeMutableBuffer<POINT>
に変換しています。
Cの構造体の配列メンバーへの書き込み
Cの構造体の配列メンバーは、Swiftのタプルにマッピングされます。タプルは一つのタイプになってしまうので扱いが厄介です。Swift側から書き込むためには、タプルのポインタを使います。
次のようなCのコードがあるとします。
typedef struct SHAPE {
char name[256];
POINT *pointList;
int numOfPoints;
} SHAPE;
void printShapeName(const SHAPE *shape)
{
printf("%s\n", shape->name);
}
このSHAPE
構造体のname
はSwiftでは、256個のInt8
で構成されるタプルになります。ここにCの文字列を書き込むSwiftのコードは次のようになります。
func tupleMutablePointer(p: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
return p
}
var shape = SHAPE()
let namePtr = tupleMutablePointer(p: &shape.name).bindMemory(to: Int8.self, capacity: 256)
strcpy(namePtr, "Triangle".cString(using: .utf8))
printShapeName(&shape)
tupleMutablePointer
は単純に引数で渡されたポインターを返すだけですが、これを入れることで、タプルのポインターを取得できます。
返されたUnsafeMutableRawPointer
からUnsafeMutablePointer<Int8>
への変換は、bindMemory(to:capacity:)
を使用します。