[2020/10/21更新] SwiftからC言語の関数を使う

2020/10/21 21:30 Cの構造体の配列メンバーへの書き込みを追加しました。

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:)を使用します。

 

スポンサーリンク
最新情報をチェックしよう!
>現役のプログラマーが書くプログラミング情報

現役のプログラマーが書くプログラミング情報

日々の開発の中での学びや分かったこと、調べたことなどを書いていくブログです。

CTR IMG