對應來自 C 的結構與聯合型別 – 教學
這是 對應 Kotlin 與 C 教學系列的第二部分。在繼續之前,請確保您已完成上一步。
對應來自 C 的基本資料型別
對應來自 C 的結構與聯合型別
對應來自 C 的函式指標
對應來自 C 的字串
C 程式庫匯入目前處於 Beta 階段。所有由 cinterop 工具從 C 程式庫產生的 Kotlin 宣告都應具有
@ExperimentalForeignApi註解。隨 Kotlin/Native 提供的原生平台程式庫(如 Foundation、UIKit 和 POSIX)僅對某些 API 需要選擇性同意(opt-in)。
讓我們探索哪些 C 結構(struct)與聯合(union)宣告在 Kotlin 中是可見的,並查看 Kotlin/Native 與 多平台 Gradle 組建中進階的 C 互通相關使用案例。
在本教學中,您將學習:
對應 C 結構與聯合型別
為了理解 Kotlin 如何對應結構與聯合型別,讓我們在 C 中宣告它們,並檢查它們在 Kotlin 中如何表示。
在 之前的教學 中,您已經建立了一個包含必要檔案的 C 程式庫。對於此步驟,請更新 interop.def 檔案中 --- 分隔符號後的宣告:
---
typedef struct {
int a;
double b;
} MyStruct;
void struct_by_value(MyStruct s) {}
void struct_by_pointer(MyStruct* s) {}
typedef union {
int a;
MyStruct b;
float c;
} MyUnion;
void union_by_value(MyUnion u) {}
void union_by_pointer(MyUnion* u) {}interop.def 檔案提供了編譯、執行或在 IDE 中開啟應用程式所需的一切。
檢查為 C 程式庫產生的 Kotlin API
讓我們看看 C 結構與聯合型別如何對應到 Kotlin/Native 並更新您的專案:
在
src/nativeMain/kotlin中,使用以下內容更新您在 之前的教學 中建立的hello.kt檔案:kotlinimport interop.* import kotlinx.cinterop.ExperimentalForeignApi @OptIn(ExperimentalForeignApi::class) fun main() { println("Hello Kotlin/Native!") struct_by_value(/* fix me*/) struct_by_pointer(/* fix me*/) union_by_value(/* fix me*/) union_by_pointer(/* fix me*/) }為避免編譯器錯誤,請將互通性加入組建過程中。為此,請使用以下內容更新您的
build.gradle(.kts)組建檔案:kotlinkotlin { macosArm64() // Apple 晶片的 macOS // linuxArm64() // ARM64 平台上的 Linux // linuxX64() // x86_64 平台上的 Linux // mingwX64() // Windows 上 targets.withType<KotlinNativeTarget>().configureEach { val main by compilations.getting val interop by main.cinterops.creating { definitionFile.set(project.file("src/nativeInterop/cinterop/interop.def")) } binaries { executable() } } }groovykotlin { macosArm64() // Apple 晶片的 macOS // linuxArm64() // ARM64 平台上的 Linux // linuxX64() // x86_64 平台上的 Linux // mingwX64() // Windows targets.withType(KotlinNativeTarget).configureEach { compilations.main.cinterops { interop { definitionFile = project.file('src/nativeInterop/cinterop/interop.def') } } binaries { executable() } } }使用 IntelliJ IDEA 的 跳轉到宣告 指令(/)來導覽至以下為 C 函式、結構與聯合產生的 API:
kotlinfun struct_by_value(s: kotlinx.cinterop.CValue<interop.MyStruct>) fun struct_by_pointer(s: kotlinx.cinterop.CValuesRef<interop.MyStruct>?) fun union_by_value(u: kotlinx.cinterop.CValue<interop.MyUnion>) fun union_by_pointer(u: kotlinx.cinterop.CValuesRef<interop.MyUnion>?)
從技術上講,在 Kotlin 端,結構與聯合型別之間沒有區別。cinterop 工具會為結構與聯合的 C 宣告產生 Kotlin 型別。
產生的 API 包含 CValue<T> 與 CValuesRef<T> 的完全限定套件名稱,反映了它們在 kotlinx.cinterop 中的位置。CValue<T> 代表按值傳遞的結構參數,而 CValuesRef<T>? 則用於傳遞指向結構或聯合的指標。
從 Kotlin 使用結構與聯合型別
由於有了產生的 API,從 Kotlin 使用 C 結構與聯合型別非常直觀。唯一的問題是如何建立這些型別的新執行個體。
讓我們看看接收 MyStruct 與 MyUnion 作為參數的產生函式。按值傳遞的參數表示為 kotlinx.cinterop.CValue<T>,而指標型別參數則使用 kotlinx.cinterop.CValuesRef<T>?。
Kotlin 提供了一個方便的 API 來建立並操作這些型別。讓我們探索如何在實務中使用它。
建立 CValue<T>
CValue<T> 型別用於將按值傳遞的參數傳遞給 C 函式呼叫。使用 cValue 函式來建立 CValue<T> 執行個體。該函式需要一個 具有接收器的 Lambda 函式 來就地初始化底層的 C 型別。該函式的宣告如下:
fun <reified T : CStructVar> cValue(initialize: T.() -> Unit): CValue<T>以下是如何使用 cValue 並傳遞按值傳遞的參數:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cValue
@OptIn(ExperimentalForeignApi::class)
fun callValue() {
val cStruct = cValue<MyStruct> {
a = 42
b = 3.14
}
struct_by_value(cStruct)
val cUnion = cValue<MyUnion> {
b.a = 5
b.b = 2.7182
}
union_by_value(cUnion)
}將結構與聯合建立為 CValuesRef<T>
CValuesRef<T> 型別在 Kotlin 中用於傳遞 C 函式的指標型別參數。若要在原生記憶體中分配 MyStruct 與 MyUnion,請在 kotlinx.cinterop.NativePlacement 型別上使用以下擴充方法:
fun <reified T : kotlinx.cinterop.CVariable> alloc(): TNativePlacement 代表原生記憶體,具有類似於 malloc 與 free 的函式。NativePlacement 有幾種實作:
全域實作是
kotlinx.cinterop.nativeHeap,但您必須呼叫nativeHeap.free()才能在使用後釋放記憶體。一個更安全的替代方案是
memScoped(),它會建立一個短期的記憶體作用域,其中的所有分配都會在區塊結束時自動釋放:kotlinfun <R> memScoped(block: kotlinx.cinterop.MemScope.() -> R): R
使用 memScoped(),您呼叫帶有指標的函式的程式碼可以如下所示:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.alloc
import kotlinx.cinterop.ptr
@OptIn(ExperimentalForeignApi::class)
fun callRef() {
memScoped {
val cStruct = alloc<MyStruct>()
cStruct.a = 42
cStruct.b = 3.14
struct_by_pointer(cStruct.ptr)
val cUnion = alloc<MyUnion>()
cUnion.b.a = 5
cUnion.b.b = 2.7182
union_by_pointer(cUnion.ptr)
}
}在這裡,可在 memScoped {} 區塊中使用的 ptr 擴充屬性,會將 MyStruct 與 MyUnion 執行個體轉換為原生指標。
由於記憶體是在 memScoped {} 區塊內管理的,它會在區塊結束時自動釋放。請避免在此作用域之外使用指標,以防止存取已釋放的記憶體。如果您需要更長期的分配(例如,為了在 C 程式庫中快取),請考慮使用 Arena() 或 nativeHeap。
CValue<T> 與 CValuesRef<T> 之間的轉換
有時您需要在一次函式呼叫中按值傳遞結構,然後在另一次呼叫中按引用傳遞相同的結構。
為此,您需要一個 NativePlacement,但首先,讓我們看看 CValue<T> 如何轉換為指標:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cValue
import kotlinx.cinterop.memScoped
@OptIn(ExperimentalForeignApi::class)
fun callMix_ref() {
val cStruct = cValue<MyStruct> {
a = 42
b = 3.14
}
memScoped {
struct_by_pointer(cStruct.ptr)
}
}在這裡同樣,來自 memScoped {} 的 ptr 擴充屬性會將 MyStruct 執行個體轉換為原生指標。這些指標僅在 memScoped {} 區塊內有效。
若要將指標轉換回按值傳遞的變數,請呼叫 .readValue() 擴充方法:
import interop.*
import kotlinx.cinterop.alloc
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.readValue
@OptIn(ExperimentalForeignApi::class)
fun callMix_value() {
memScoped {
val cStruct = alloc<MyStruct>()
cStruct.a = 42
cStruct.b = 3.14
struct_by_value(cStruct.readValue())
}
}更新 Kotlin 程式碼
既然您已經學習了如何在 Kotlin 程式碼中使用 C 宣告,請嘗試在您的專案中使用它們。hello.kt 檔案中的最終程式碼可能如下所示:
import interop.*
import kotlinx.cinterop.alloc
import kotlinx.cinterop.cValue
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.readValue
import kotlinx.cinterop.ExperimentalForeignApi
@OptIn(ExperimentalForeignApi::class)
fun main() {
println("Hello Kotlin/Native!")
val cUnion = cValue<MyUnion> {
b.a = 5
b.b = 2.7182
}
memScoped {
union_by_value(cUnion)
union_by_pointer(cUnion.ptr)
}
memScoped {
val cStruct = alloc<MyStruct> {
a = 42
b = 3.14
}
struct_by_value(cStruct.readValue())
struct_by_pointer(cStruct.ptr)
}
}若要驗證一切是否如預期運作,請 在您的 IDE 中 執行 runDebugExecutable<YourTargetName> Gradle 任務,或在您的終端機中使用命令列指令,在此範例中為:
./gradlew runDebugExecutableMacosArm64下一步
在本系列的下一部分中,您將學習函式指標如何在 Kotlin 與 C 之間進行對應:
延伸閱讀
在涵蓋更多進階案例的 與 C 互通 文件中了解更多資訊。
