Skip to content

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

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

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

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

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

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

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

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

C言語の型

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

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

さらに、より具体的な型もあります。

  • 真偽値型(C99より)
  • size_tptrdiff_tssize_tも)
  • 固定幅整数型(例: int32_t または uint64_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スレッドを参照してください。

  1. 以下の内容でlib.def 定義ファイルを作成します。
c
headers = lib.h
  1. 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
    plugins {
        kotlin("multiplatform") version "2.2.10"
    }
    
    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.14"
        distributionType = Wrapper.DistributionType.BIN
    }
    groovy
    plugins {
        id 'org.jetbrains.kotlin.multiplatform' version '2.2.10'
    }
    
    repositories {
        mavenCentral()
    }
    
    kotlin {
        macosArm64("native") {    // Apple Silicon macOS
        // macosX64("native") {   // macOS on x86_64 platforms
        // linuxArm64("native") { // Linux on ARM64 platforms
        // linuxX64("native") {   // Linux on x86_64 platforms
        // mingwX64("native") {   // Windows
            compilations.main.cinterops {
                interop 
            }
        
            binaries {
                executable()
            }
        }
    }
    
    wrapper {
        gradleVersion = '8.14'
        distributionType = 'BIN'
    }

このプロジェクトファイルは、C相互運用をビルドステップとして追加で設定します。 設定のさまざまな方法については、Multiplatform Gradle DSL リファレンスを参照してください。

  1. interop.deflib.hlib.defファイルをsrc/nativeInterop/cinteropディレクトリに移動します。
  2. src/nativeMain/kotlinディレクトリを作成します。Gradleの規約に従い、ここにすべてのソースファイルを配置します。

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

  1. 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型は通常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でrunDebugExecutableNative Gradleタスクを実行するか、以下のコマンドを使用してコードを実行します。

bash
./gradlew runDebugExecutableNative

次のステップ

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

次のパートに進む

関連項目

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