Skip to content

Cからのプリミティブデータ型のマッピング – チュートリアル

これは Mapping Kotlin and C(KotlinとCのマッピング)チュートリアルシリーズの第1部です。

First step Cからのプリミティブデータ型のマッピング
Second step Cからの構造体(struct)型と共用体(union)型のマッピング
Third step Cからの関数ポインタのマッピング
Fourth step Cからの文字列のマッピング

Cライブラリのインポートはベータ版です。cinteropツールによってCライブラリから生成されたすべてのKotlin宣言には、@ExperimentalForeignApi アノテーションが付与されます。

Kotlin/Nativeに同梱されているネイティブプラットフォームライブラリ(Foundation、UIKit、POSIXなど)では、一部のAPIについてのみオプトインが必要です。

どのCデータ型がKotlin/Nativeで参照可能か(またはその逆)を探り、Kotlin/Nativeとマルチプラットフォーム Gradleビルドにおける、高度なC相互運用(interop)に関連するユースケースを確認しましょう。

このチュートリアルでは、以下の内容を学習します:

コマンドラインを使用して、直接またはスクリプトファイル(.sh.bat ファイルなど)でKotlinライブラリを生成できます。 しかし、このアプローチは数百のファイルやライブラリを持つ大規模なプロジェクトには適していません。 ビルドシステムを使用すると、Kotlin/Nativeコンパイラのバイナリや推移的依存関係(transitive dependencies)を持つライブラリのダウンロードとキャッシュ、およびコンパイラとテストの実行が簡素化されます。 Kotlin/Nativeは、Kotlinマルチプラットフォームプラグインを通じて Gradle ビルドシステムを使用できます。

C言語の型

Cプログラミング言語には、以下のデータ型があります:

  • 基本型:char, int, float, double と、修飾子の signed, unsigned, short, long
  • 構造体(Structures)、共用体(Unions)、配列(Arrays)
  • ポインタ(Pointers)
  • 関数ポインタ(Function pointers)

また、より具体的な型もあります:

  • ブーリアン型(C99 より)
  • size_t および ptrdiff_t(および ssize_t
  • int32_tuint64_t などの固定幅整数型(C99 より)

また、C言語には constvolatilerestrictatomic といった型修飾子も存在します。

これらのCデータ型がKotlinでどのように見えるかを見ていきましょう。

Cライブラリの作成

このチュートリアルでは、lib.c ソースファイルは作成しません。これはCライブラリをコンパイルして実行する場合にのみ必要です。今回のセットアップでは、cinteropツールを実行するために必要な .h ヘッダーファイルのみが必要です。

cinteropツールは、一連の .h ファイルに対してKotlin/Nativeライブラリ(.klib ファイル)を生成します。生成されたライブラリは、Kotlin/NativeからCへの呼び出しをブリッジするのに役立ちます。これには、.h ファイルの定義に対応するKotlin宣言が含まれています。

Cライブラリを作成するには:

  1. 将来のプロジェクト用に空のフォルダを作成します。

  2. その中に、Cの関数がどのようにKotlinにマッピングされるかを確認するための lib.h ファイルを、以下の内容で作成します:

    c
    #ifndef LIB2_H_INCLUDED
    #define LIB2_H_INCLUDED
    
    void ints(char c, short d, int e, long f);
    void uints(unsigned char c, unsigned short d, unsigned int e, unsigned long f);
    void doubles(float a, double b);
    
    #endif

    このファイルには extern "C" ブロックが含まれていません。この例では不要ですが、C++を使用し関数をオーバーロードする場合は必要になることがあります。詳細は、こちらの Stackoverflowのスレッド を参照してください。

  3. 以下の内容で lib.def 定義ファイル(definition file)を作成します:

    c
    headers = lib.h
  4. cinteropツールによって生成されるコードに、マクロやその他のCの定義を含めると便利な場合があります。これにより、メソッド本体もコンパイルされ、バイナリに完全に含まれるようになります。この機能を使用すると、Cコンパイラを必要とせずに実行可能な例を作成できます。

    これを行うには、lib.h ファイルのC関数の実装を、新しい interop.def ファイルの --- セパレータの後に追加します:

    c
    
    ---
     
    void ints(char c, short d, int e, long f) { }
    void uints(unsigned char c, unsigned short d, unsigned int e, unsigned long f) { }
    void doubles(float a, double b) { }

interop.def ファイルは、アプリケーションをIDEでコンパイル、実行、または開くために必要なすべてを提供します。

Kotlin/Nativeプロジェクトの作成

初めてのステップの詳細や、新しいKotlin/Nativeプロジェクトを作成してIntelliJ IDEAで開く方法については、Kotlin/Nativeを始める チュートリアルを参照してください。

プロジェクトファイルを作成するには:

  1. プロジェクトフォルダに、以下の内容で build.gradle(.kts) Gradleビルドファイルを作成します:

    kotlin
    import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
    
    plugins {
        kotlin("multiplatform") version "2.3.0"
    }
    
    repositories {
        mavenCentral()
    }
    
    kotlin {
        macosArm64()    // Apple Silicon搭載のmacOS
        // linuxArm64() // ARM64プラットフォーム上のLinux
        // linuxX64()   // x86_64プラットフォーム上のLinux
        // mingwX64()   // x86_64プラットフォーム上のWindows
    
        targets.withType<KotlinNativeTarget>().configureEach {
            val main by compilations.getting
            val interop by main.cinterops.creating
    
            binaries {
                executable()
            }
        }
    }
    
    tasks.wrapper {
        gradleVersion = "9.0.0"
        distributionType = Wrapper.DistributionType.BIN
    }
    groovy
    import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
    
    plugins {
        id 'org.jetbrains.kotlin.multiplatform' version '2.3.0'
    }
    
    repositories {
        mavenCentral()
    }
    
    kotlin {
        macosArm64()    // Apple Silicon搭載のmacOS
        // linuxArm64() // ARM64プラットフォーム上のLinux
        // linuxX64()   // x86_64プラットフォーム上のLinux
        // mingwX64()   // Windows
    
        targets.withType(KotlinNativeTarget).configureEach {
            compilations.main.cinterops {
                interop
            }
    
            binaries {
                executable()
            }
        }
    }
    
    wrapper {
        gradleVersion = '9.0.0'
        distributionType = 'BIN'
    }

    このプロジェクトファイルは、C相互運用(interop)を追加のビルドステップとして構成します。 構成のさまざまな方法については、マルチプラットフォーム Gradle DSL リファレンスを確認してください。

  2. interop.deflib.h、および lib.def ファイルを src/nativeInterop/cinterop ディレクトリに移動します。

  3. src/nativeMain/kotlin ディレクトリを作成します。ここは、設定(configurations)ではなく規約(conventions)を使用するというGradleの推奨事項に従って、すべてのソースファイルを配置する場所です。

    デフォルトでは、Cからのすべてのシンボルは interop パッケージにインポートされます。

  4. src/nativeMain/kotlin に、以下の内容で hello.kt スタブファイルを作成します:

    kotlin
    import interop.*
    import kotlinx.cinterop.ExperimentalForeignApi
    
    @OptIn(ExperimentalForeignApi::class)
    fun main() {
        println("Hello Kotlin/Native!")
    
        ints(/* fix me*/)
        uints(/* fix me*/)
        doubles(/* fix me*/)
    }

Cのプリミティブ型の宣言がKotlin側からどのように見えるかを学習した後、コードを完成させます。

Cライブラリに対して生成されたKotlin APIを検査する

Cのプリミティブ型がKotlin/Nativeにどのようにマッピングされるかを確認し、それに応じてサンプルプロジェクトを更新しましょう。

IntelliJ IDEAの 宣言へ移動(Go to declaration) コマンド(/) を使用して、C関数に対して生成された以下のAPIに移動します:

kotlin
fun ints(c: kotlin.Byte, d: kotlin.Short, e: kotlin.Int, f: kotlin.Long)
fun uints(c: kotlin.UByte, d: kotlin.UShort, e: kotlin.UInt, f: kotlin.ULong)
fun doubles(a: kotlin.Float, b: kotlin.Double)

Cの型は直接マッピングされますが、char 型だけは例外です。char は通常8ビットの符号付き数値であるため、kotlin.Byte にマッピングされます:

CKotlin
charkotlin.Byte
unsigned charkotlin.UByte
shortkotlin.Short
unsigned shortkotlin.UShort
intkotlin.Int
unsigned intkotlin.UInt
long longkotlin.Long
unsigned long longkotlin.ULong
floatkotlin.Float
doublekotlin.Double

Kotlinコードの更新

Cの定義を確認したので、Kotlinコードを更新できます。hello.kt ファイルの最終的なコードは以下のようになります:

kotlin
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi

@OptIn(ExperimentalForeignApi::class)
fun main() {
    println("Hello Kotlin/Native!")

    ints(1, 2, 3, 4)
    uints(5u, 6u, 7u, 8u)
    doubles(9.0f, 10.0)
}

すべてが期待通りに動作することを確認するには、IDEで runDebugExecutable<YourTargetName> Gradleタスクを実行するか、ターミナルでコンソールコマンドを使用してコードを実行します。この例では以下の通りです:

bash
./gradlew runDebugExecutableMacosArm64

次のステップ

シリーズの次のパートでは、構造体(struct)型と共用体(union)型がKotlinとCの間でどのようにマッピングされるかを学びます:

次のパートに進む

関連項目

より高度なシナリオをカバーしている Cとの相互運用性 ドキュメントで詳細を確認してください。