Skip to content

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

これはKotlinとCのマッピングチュートリアルシリーズの最初のパートです。

最初のステップ Cのプリミティブデータ型のマッピング
2番目のステップ Cの構造体と共用体型のマッピング
3番目のステップ 関数ポインターのマッピング
4番目のステップ Cの文字列のマッピング

DANGER

Cライブラリのインポートは実験的機能 (Experimental)です。cinteropツールによってCライブラリから生成されるKotlinの宣言は、すべて@ExperimentalForeignApiアノテーションを持つ必要があります。

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

どのCデータ型がKotlin/Nativeで可視であるか、またその逆についても見ていきましょう。さらに、Kotlin/NativeのC言語との相互運用に関連する高度なユースケースや、マルチプラットフォームのGradleビルドについて詳しく見ていきます。

このチュートリアルでは、次のことを行います:

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

C言語のデータ型

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

  • 基本型: char, int, float, double (修飾子 signed, unsigned, short, long を伴う)
  • 構造体、共用体、配列
  • ポインター
  • 関数ポインター

また、より具体的な型も存在します:

  • 真偽値型 (Boolean型)(C99より)
  • size_t および ptrdiff_tssize_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定義ファイルを作成します:

    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プロジェクトを作成する

TIP

詳細な最初のステップについては、Kotlin/Nativeを始めるチュートリアルを参照してください。および新しいKotlin/Nativeプロジェクトを作成し、IntelliJ IDEAで開く方法に関する指示についても記載されています。

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

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

kotlin
    plugins {
        kotlin("multiplatform") version "2.1.21"
    }
    
    repositories {
        mavenCentral()
    }
    
    kotlin {
        macosArm64("native") {    // Apple Silicon上のmacOS
        // macosX64("native") {   // x86_64プラットフォーム上のmacOS
        // linuxArm64("native") { // ARM64プラットフォーム上のLinux 
        // linuxX64("native") {   // x86_64プラットフォーム上のLinux
        // mingwX64("native") {   // Windows上
            val main by compilations.getting
            val interop by main.cinterops.creating
        
            binaries {
                executable()
            }
        }
    }
    
    tasks.wrapper {
        gradleVersion = "8.10"
        distributionType = Wrapper.DistributionType.BIN
    }
    ```
```groovy [Groovy]
    plugins {
        id 'org.jetbrains.kotlin.multiplatform' version '2.1.21'
    }
    
    repositories {
        mavenCentral()
    }
    
    kotlin {
        macosArm64("native") {    // Apple Silicon macOS
        // macosX64("native") {   // x86_64プラットフォーム上のmacOS
        // linuxArm64("native") { // ARM64プラットフォーム上のLinux
        // linuxX64("native") {   // x86_64プラットフォーム上のLinux
        // mingwX64("native") {   // Windows
            compilations.main.cinterops {
                interop 
            }
        
            binaries {
                executable()
            }
        }
    }
    
    wrapper {
        gradleVersion = '8.10'
        distributionType = 'BIN'
    }
    ```
:::

   このプロジェクトファイルは、C相互運用をビルドの追加ステップとして構成します。さまざまな構成方法を学ぶために、[Multiplatform Gradle DSLリファレンス](https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-dsl-reference.html)を確認してください。

2. `interop.def`、`lib.h`、`lib.def`ファイルを`src/nativeInterop/cinterop`ディレクトリに移動します。
3. `src/nativeMain/kotlin`ディレクトリを作成します。これは、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の[宣言に移動](https://www.jetbrains.com/help/rider/Navigation_and_Search__Go_to_Declaration.html)コマンド(<shortcut>Cmd + B</shortcut>/<shortcut>Ctrl + B</shortcut>)を使用して、以下の生成されたAPI(C関数用)に移動します:

```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型は通常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)
}

すべてが期待通りに動作することを確認するために、IDErunDebugExecutableNativeGradleタスクを実行するか、以下のコマンドを使用してコードを実行します:

bash
./gradlew runDebugExecutableNative

次のステップ

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

次のパートに進む

参照

より高度なシナリオをカバーするCとの相互運用ドキュメントで詳細を学びましょう。