Cからの関数ポインタのマッピング – チュートリアル
これは「KotlinとCのマッピング」チュートリアルシリーズの第3部です。次に進む前に、前のステップを完了していることを確認してください。
Cからのプリミティブデータ型のマッピング
Cからの構造体および共用体型のマッピング
Cからの関数ポインタのマッピング
Cからの文字列のマッピング
Cライブラリのインポートはベータ段階です。cinteropツールによってCライブラリから生成されたすべてのKotlin宣言には、
@ExperimentalForeignApiアノテーションが付与されている必要があります。Kotlin/Nativeに同梱されているネイティブプラットフォームライブラリ(Foundation、UIKit、POSIXなど)では、一部のAPIでのみオプトインが必要です。
KotlinからどのC関数ポインタが見えるかを確認し、Kotlin/Nativeとマルチプラットフォーム Gradleビルドの高度なC相互運用(interop)に関するユースケースを調べましょう。
このチュートリアルでは、以下の内容を学びます:
Cからの関数ポインタ型のマッピング
KotlinとCの間のマッピングを理解するために、2つの関数を宣言してみましょう。1つは関数ポインタをパラメータとして受け取り、もう1つは関数ポインタを返す関数です。
シリーズの第1部で、必要なファイルを含む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()関数はオプション(nullable)の関数ポインタをパラメータとして取り、supply_fun()は関数ポインタを返します。
CFunction<(Int) -> Int>は関数のシグネチャを表し、CPointer<CFunction<...>>?はnullableな関数ポインタを表します。すべての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関数ポインタにラップしています。これには、バインドされていない、キャプチャを行わない(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は、関数ポインタの戻り値をnullableなCPointer<CFunction<>>オブジェクトに変換します。最初に明示的にnullチェックを行う必要があり、そのため上記のコードではエルビス演算子が使用されています。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で runDebugExecutable<YourTargetName> Gradleタスクを実行するか、ターミナルでコンソールコマンドを使用します。この例では以下の通りです:
./gradlew runDebugExecutableMacosArm64次のステップ
シリーズの次のパートでは、KotlinとCの間で文字列がどのようにマッピングされるかを学びます:
関連項目
より高度なシナリオをカバーしているCとの相互運用性のドキュメントで詳細を確認してください。
