メインコンテンツまでスキップ

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

備考

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

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

ヒント

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

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

Kotlin/NativeでどのCデータ型が表示されるか、またその逆も調べ、Kotlin/NativeとマルチプラットフォームGradleビルドのC interop関連の高度なユースケースを検証しましょう。

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

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

C言語の型

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

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

より具体的な型もあります。

  • ブール型(C99以降)
  • size_tptrdiff_tssize_t も)
  • 固定幅整数型(int32_tuint64_t など)(C99以降)

C言語には、次の型修飾子もあります:const, volatile, restrict, atomic

KotlinでどのCデータ型が表示されるかを見てみましょう。

Cライブラリを作成する

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

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

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

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

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

    #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 threadを参照してください。

  3. 次の内容でlib.def定義ファイルを作成します。

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

    これを行うには、---区切り文字の後に、lib.hファイルからのC関数の実装を新しいinterop.defファイルに追加します。


    ---

    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ビルドファイルを作成します。

    plugins {
    kotlin("multiplatform") version "2.1.20"
    }

    repositories {
    mavenCentral()
    }

    kotlin {
    macosArm64("native") { // macOS on Apple Silicon
    // macosX64("native") { // macOS on x86_64 platforms
    // linuxArm64("native") { // Linux on ARM64 platforms
    // linuxX64("native") { // Linux on x86_64 platforms
    // mingwX64("native") { // on Windows
    val main by compilations.getting
    val interop by main.cinterops.creating

    binaries {
    executable()
    }
    }
    }

    tasks.wrapper {
    gradleVersion = "8.10"
    distributionType = Wrapper.DistributionType.BIN
    }

    プロジェクトファイルは、C interopを追加のビルドステップとして構成します。 さまざまな構成方法については、Multiplatform Gradle DSL referenceを参照してください。

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

  3. src/nativeMain/kotlinディレクトリを作成します。これは、構成の代わりに規約を使用するというGradleの推奨事項に従って、すべてのソースファイルを配置する必要がある場所です。

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

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

    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コマンド(Cmd + B/Ctrl + B)を使用して、C関数のために生成された次のAPIに移動します。

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)

char型を除いて、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ファイルの最終的なコードは次のようになります。

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でrunDebugExecutableNative Gradleタスクを実行するか、次のコマンドを使用してコードを実行します。

./gradlew runDebugExecutableNative

次のステップ

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

次のパートに進む

参照

より高度なシナリオをカバーするInteroperability with Cドキュメントで詳細をご覧ください。