Pebble's Diary

プログラマーの作業メモ

swiftのクラスのポインタをC言語に渡してコールバック後にクラスに戻す(swift3)

[2017-02-12 update] swift3からunsafeBitCastではなくUnmanagedを使う方法が推奨となっていますのでそちらをお使いください。

unsafeBitCastを使ってswiftのクラスをポインタとして扱い、コールバック後のクラスに戻して動作するかやってみた。
結論から言うと動いた。これでAppleフレームワークのせいでObj-Cを書かなくてはいけないケースは無くなった。
swiftからコールバック関数を利用するC言語SDKを使う場合、これは基本的なテクニックになりそうだ。

XXX-Bridging-Header.h

#include "cfile.h"

cfile.h

typedef void (*CALLBACK)(void*, int);
void registerCallback(CALLBACK callback, void* ref);
void invoke(void);

cfile.c

#include "cfile.h"

static CALLBACK s_callback;
static void* s_ref;

void registerCallback(CALLBACK callback, void* ref)
{
    s_callback = callback;
    s_ref = ref;
}

void invoke(void)
{
    for (int i = 0; i < 5; ++i) {
        (*s_callback)(s_ref, i);
    }
}

main.swift

class Engine {
    let callback: @convention(c) (UnsafeMutableRawPointer?, Int32) -> Void = {
        (ref, count) in
        if let ref_unwrapped = ref {
            let engine:Engine = unsafeBitCast(ref_unwrapped, to:Engine.self)
            let ret = engine.square(val:count)
            print("\(ret)")
        }
    }
    init(){
        let ref: UnsafeMutableRawPointer = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
        registerCallback(callback, ref);
    }
    func execute() {
        invoke()
    }
    func square(val:Int32) -> Int32
    {
        return val*val
    }
}
// 0
// 1
// 4
// 9
// 16

[2017-01-21 改訂]
swift3になってvoid*型はオプショナルに変化しました。
nilかどうかチェックできるので少し安全性が高まりました。