Cの関数ポインタのマッピング – チュートリアル
これはKotlinとCのマッピングチュートリアルシリーズの第3部です。次に進む前に、前の手順を完了していることを確認してください。
Cのプリミティブデータ型のマッピング
Cの構造体と共用体型のマッピング
関数ポインタのマッピング
Cの文字列のマッピング
DANGER
Cライブラリのインポートは実験的です。cinteropツールによってCライブラリから生成されるすべてのKotlin宣言には、@ExperimentalForeignApi
アノテーションが付加されるべきです。
Kotlin/Nativeに同梱されているネイティブプラットフォームライブラリ(Foundation、UIKit、POSIXなど)は、一部のAPIでのみオプトインが必要です。
KotlinからどのC関数ポインタが見えるか、またKotlin/NativeとマルチプラットフォームGradleビルドの高度なC相互運用関連のユースケースを見ていきましょう。
このチュートリアルでは、次のことを行います。
Cから関数ポインタ型をマッピングする
KotlinとC間のマッピングを理解するために、関数ポインタをパラメータとして受け取る関数と、関数ポインタを返す関数の2つを宣言してみましょう。
シリーズの最初のパートで、必要なファイルを含むCライブラリをすでに作成しました。この手順では、---
セパレータの後にinterop.def
ファイル内の宣言を更新します。
---
int myFun(int i) {
return i+1;
}
typedef int (*MyFun)(int);
void accept_fun(MyFun f) {
f(42);
}
MyFun supply_fun() {
return myFun;
}
interop.def
ファイルは、アプリケーションをコンパイル、実行、またはIDEで開くために必要なすべてを提供します。
Cライブラリ用に生成されたKotlin APIを検査する
C関数ポインタがどのようにKotlin/Nativeにマッピングされるかを見て、プロジェクトを更新しましょう。
src/nativeMain/kotlin
にあるhello.kt
ファイルを、前のチュートリアルの内容で以下のように更新します。kotlinimport interop.* import kotlinx.cinterop.ExperimentalForeignApi @OptIn(ExperimentalForeignApi::class) fun main() { println("Hello Kotlin/Native!") accept_fun(/* fix me*/) val useMe = supply_fun() }
IntelliJ IDEAのGo to declarationコマンド(/)を使用して、C関数用に生成された以下のAPIに移動します。
kotlinfun myFun(i: kotlin.Int): kotlin.Int fun accept_fun(f: kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>>? /* from: interop.MyFun? */) fun supply_fun(): kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>>? /* from: interop.MyFun? */
ご覧のとおり、C関数ポインタはKotlinではCPointer<CFunction<...>>
を使用して表現されます。accept_fun()
関数はオプションの関数ポインタをパラメータとして受け取り、supply_fun()
は関数ポインタを返します。
CFunction<(Int) -> Int>
は関数シグネチャを表し、CPointer<CFunction<...>>?
はnull許容の関数ポインタを表します。すべてのCPointer<CFunction<...>>
型でinvoke
演算子拡張関数が利用可能であり、それにより関数ポインタを通常のKotlin関数であるかのように呼び出すことができます。
Kotlin関数をC関数ポインタとして渡す
KotlinコードからC関数を使ってみましょう。accept_fun()
関数を呼び出し、C関数ポインタをKotlinラムダに渡します。
import interop.*
import kotlinx.cinterop.staticCFunction
import kotlinx.cinterop.ExperimentalForeignApi
@OptIn(ExperimentalForeignApi::class)
fun myFun() {
accept_fun(staticCFunction<Int, Int> { it + 1 })
}
この呼び出しは、Kotlin/NativeのstaticCFunction {}
ヘルパー関数を使用して、Kotlinラムダ関数をC関数ポインタにラップします。これにより、束縛されていない(unbound)ラムダ関数と、キャプチャしない(non-capturing)ラムダ関数のみが許可されます。たとえば、関数のローカル変数をキャプチャすることはできず、グローバルに可視な宣言のみが対象となります。
関数が例外をスローしないことを確認してください。staticCFunction {}
から例外をスローすると、非決定論的な副作用が発生します。
KotlinからC関数ポインタを使用する
次のステップは、supply_fun()
の呼び出しから返されたC関数ポインタを呼び出すことです。
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.invoke
@OptIn(ExperimentalForeignApi::class)
fun myFun2() {
val functionFromC = supply_fun() ?: error("No function is returned")
functionFromC(42)
}
Kotlinは、関数ポインタの戻り値をnull許容のCPointer<CFunction<>>
オブジェクトに変換します。まず明示的にnull
をチェックする必要があります。そのため、上記のコードではElvis演算子が使用されています。cinteropツールを使用すると、C関数ポインタを通常のKotlin関数呼び出し(例: functionFromC(42)
)として呼び出すことができます。
Kotlinコードを更新する
すべての定義を確認したので、プロジェクトでそれらを使用してみましょう。 hello.kt
ファイルのコードは次のようになります。
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.invoke
import kotlinx.cinterop.staticCFunction
@OptIn(ExperimentalForeignApi::class)
fun main() {
println("Hello Kotlin/Native!")
val cFunctionPointer = staticCFunction<Int, Int> { it + 1 }
accept_fun(cFunctionPointer)
val funFromC = supply_fun() ?: error("No function is returned")
funFromC(42)
}
すべてが期待どおりに機能することを確認するには、IDEでrunDebugExecutableNative
Gradleタスクを実行するか、以下のコマンドを使用してコードを実行します。
./gradlew runDebugExecutableNative
次のステップ
シリーズの次のパートでは、文字列がKotlinとCの間でどのようにマッピングされるかを学びます。
参照
より高度なシナリオをカバーするCとの相互運用ドキュメントで詳細を学ぶことができます。