C interopとlibcurlを使用してアプリを作成する – チュートリアル
このチュートリアルでは、IntelliJ IDEA を使用してコマンドラインアプリケーションを作成する方法を説明します。Kotlin/Native と libcurl ライブラリを使用して、指定されたプラットフォームでネイティブに実行できるシンプルな HTTP クライアントを作成する方法を学びます。
出力されるのは、macOS および Linux で実行し、簡単な HTTP GET リクエストを実行できる実行可能なコマンドラインアプリです。
コマンドラインを使用して、Kotlin ライブラリを直接、またはスクリプトファイル (例: .sh
や .bat
ファイル) で生成できます。しかし、このアプローチは、数百のファイルとライブラリを持つ大規模なプロジェクトにはスケールしません。ビルドシステムを使用することで、Kotlin/Native コンパイラのバイナリや、推移的依存関係を持つライブラリのダウンロードとキャッシュ、コンパイラとテストの実行を自動化し、プロセスを簡素化します。Kotlin/Native は、Kotlin Multiplatform プラグインを通じて Gradle ビルドシステムを使用できます。
始める前に
IntelliJ IDEA の最新バージョンをダウンロードしてインストールします。
IntelliJ IDEA で File | New | Project from Version Control を選択し、次の URL を使用して プロジェクトテンプレート をクローンします。
nonehttps://github.com/Kotlin/kmp-native-wizard
プロジェクト構造を確認します。
このテンプレートには、作業を開始するために必要なファイルとフォルダを含むプロジェクトが含まれています。Kotlin/Native で記述されたアプリケーションは、コードにプラットフォーム固有の要件がない場合、異なるプラットフォームをターゲットにできることを理解しておくことが重要です。コードは
nativeMain
ディレクトリに配置され、対応するnativeTest
もあります。このチュートリアルでは、フォルダ構造はそのまま維持します。プロジェクト設定を含むビルドスクリプトである
build.gradle.kts
ファイルを開きます。ビルドファイルで以下の点に特に注意してください。kotlinkotlin { val hostOs = System.getProperty("os.name") val isArm64 = System.getProperty("os.arch") == "aarch64" val isMingwX64 = hostOs.startsWith("Windows") val nativeTarget = when { hostOs == "Mac OS X" && isArm64 -> macosArm64("native") hostOs == "Mac OS X" && !isArm64 -> macosX64("native") hostOs == "Linux" && isArm64 -> linuxArm64("native") hostOs == "Linux" && !isArm64 -> linuxX64("native") isMingwX64 -> mingwX64("native") else -> throw GradleException("Host OS is not supported in Kotlin/Native.") } nativeTarget.apply { binaries { executable { entryPoint = "main" } } } }
- ターゲットは、macOS、Linux、Windows 用に
macosArm64
、macosX64
、linuxArm64
、linuxX64
、mingwX64
を使用して定義されます。サポートされているプラットフォームの完全なリストは、こちら を参照してください。 - エントリ自体は、バイナリがどのように生成されるか、およびアプリケーションのエントリポイントを示す一連のプロパティを定義します。これらはデフォルト値のままにしておくことができます。
- C相互運用性は、ビルドの追加ステップとして構成されます。デフォルトでは、Cからのすべてのシンボルが
interop
パッケージにインポートされます。.kt
ファイルでパッケージ全体をインポートしたい場合があります。設定方法の詳細については、こちらを参照してください。
- ターゲットは、macOS、Linux、Windows 用に
定義ファイルの作成
ネイティブアプリケーションを記述する際、HTTP リクエストの作成、ディスクからの読み書きなど、Kotlin 標準ライブラリに含まれていない特定の機能にアクセスする必要があることがよくあります。
Kotlin/Native は、標準 C ライブラリの利用を支援し、必要なほぼすべての機能が存在するエコシステム全体を開放します。Kotlin/Native にはすでに、標準ライブラリにいくつかの追加の共通機能を提供する、事前構築された一連のプラットフォームライブラリが同梱されています。
相互運用 (interop) の理想的なシナリオは、C 関数を Kotlin 関数を呼び出すかのように、同じシグネチャと規約に従って呼び出すことです。ここで cinterop ツールが役立ちます。C ライブラリを取り込み、対応する Kotlin バインディングを生成することで、そのライブラリを Kotlin コードであるかのように使用できるようになります。
これらのバインディングを生成するには、各ライブラリに定義ファイルが必要です。これは通常、ライブラリと同じ名前です。これは、ライブラリがどのように利用されるべきかを正確に記述するプロパティファイルです。
このアプリでは、いくつかの HTTP 呼び出しを行うために libcurl ライブラリが必要です。その定義ファイルを作成するには:
src
フォルダを選択し、File | New | Directory で新しいディレクトリを作成します。新しいディレクトリの名前を nativeInterop/cinterop にします。これはヘッダーファイルの場所のデフォルトの慣習ですが、別の場所を使用する場合は
build.gradle.kts
ファイルでオーバーライドできます。この新しいサブフォルダを選択し、File | New | File で新しい
libcurl.def
ファイルを作成します。ファイルを次のコードで更新します。
cheaders = curl/curl.h headerFilter = curl/* compilerOpts.linux = -I/usr/include -I/usr/include/x86_64-linux-gnu linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurl
headers
は、Kotlin スタブを生成するヘッダーファイルのリストです。このエントリに複数のファイルを追加でき、それぞれをスペースで区切ります。このケースでは、curl.h
のみです。参照されるファイルは、指定されたパス (このケースでは/usr/include/curl
) で利用可能である必要があります。headerFilter
は、何が正確に含まれるかを示します。C では、あるファイルが#include
ディレクティブで別のファイルを参照すると、すべてのヘッダーも含まれます。時にはこれは必要ない場合があり、glob パターン を使用してこのパラメータを追加し、調整できます。外部依存関係 (システム
stdint.h
ヘッダーなど) を相互運用ライブラリにフェッチしたくない場合は、headerFilter
を使用できます。また、ライブラリサイズの最適化や、システムと提供される Kotlin/Native コンパイル環境間の潜在的な競合の修正にも役立つ場合があります。特定のプラットフォームの動作を変更する必要がある場合は、
compilerOpts.osx
やcompilerOpts.linux
のような形式を使用して、オプションにプラットフォーム固有の値を指定できます。このケースでは、macOS (.osx
サフィックス) と Linux (.linux
サフィックス) です。サフィックスなしのパラメータ (例:linkerOpts=
) も可能であり、すべてのプラットフォームに適用されます。
利用可能なオプションの完全なリストについては、定義ファイルを参照してください。
NOTE
サンプルを動作させるには、システムに curl
ライブラリのバイナリが必要です。macOS と Linux では、通常含まれています。Windows では、ソースからビルドできます (Microsoft Visual Studio または Windows SDK コマンドラインツールが必要です)。詳細については、関連ブログ記事を参照してください。あるいは、MinGW/MSYS2 の curl
バイナリを検討することもできます。
ビルドプロセスへの相互運用性の追加
ヘッダーファイルを使用するには、それらがビルドプロセスの一部として生成されるようにします。そのためには、build.gradle.kts
ファイルに次のエントリを追加します。
nativeTarget.apply {
compilations.getByName("main") {
cinterops {
val libcurl by creating
}
}
binaries {
executable {
entryPoint = "main"
}
}
}
まず cinterops
が追加され、次に定義ファイルのエントリが追加されます。デフォルトでは、ファイル名が使用されます。これを追加のパラメータでオーバーライドできます。
cinterops {
val libcurl by creating {
definitionFile.set(project.file("src/nativeInterop/cinterop/libcurl.def"))
packageName("com.jetbrains.handson.http")
compilerOpts("-I/path")
includeDirs.allHeaders("path")
}
}
アプリケーションコードの記述
これで、ライブラリと対応する Kotlin スタブができたので、アプリケーションからそれらを使用できます。このチュートリアルでは、simple.c の例を Kotlin に変換します。
src/nativeMain/kotlin/
フォルダにある Main.kt
ファイルを次のコードで更新します。
import kotlinx.cinterop.*
import libcurl.*
@OptIn(ExperimentalForeignApi::class)
fun main(args: Array<String>) {
val curl = curl_easy_init()
if (curl != null) {
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com")
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)
val res = curl_easy_perform(curl)
if (res != CURLE_OK) {
println("curl_easy_perform() failed ${curl_easy_strerror(res)?.toKString()}")
}
curl_easy_cleanup(curl)
}
}
ご覧のとおり、Kotlin 版では明示的な変数宣言は省略されていますが、その他は C 版とほぼ同じです。libcurl ライブラリで期待されるすべての呼び出しは、Kotlin の同等物で利用できます。
TIP
これは逐語的な直訳です。これをより Kotlin らしい書き方で記述することもできます。
アプリケーションのコンパイルと実行
アプリケーションをコンパイルします。そのためには、タスクリストから
runDebugExecutableNative
Gradle タスクを実行するか、ターミナルで次のコマンドを使用します。bash./gradlew runDebugExecutableNative
この場合、cinterop ツールによって生成される部分は、ビルドに暗黙的に含まれます。
コンパイル中にエラーがない場合は、
main()
関数の隣にあるガターの緑色の Run アイコンをクリックするか、/ ショートカットを使用します。IntelliJ IDEA が Run タブを開き、出力 — example.com のコンテンツ — を表示します。
curl_easy_perform
の呼び出しが結果を標準出力に出力するため、実際の出力を確認できます。curl_easy_setopt
を使用してこれを非表示にすることもできます。
NOTE
完全なプロジェクトコードは、GitHub リポジトリで入手できます。
次のステップ
Kotlin の C との相互運用性についてさらに詳しく学びましょう。