From 844e0628c06549320e14b8197779cb9561b0aa1a Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 30 Jul 2024 17:23:46 +0200 Subject: [PATCH] Support String as parameter for FFI functions --- .gitignore | 1 + .../korlibs/ffi/ksp/FFIBuilderProcessor.kt | 43 ++++++++++++++++--- korlibs-ffi/src/korlibs/ffi/api/FFI.kt | 3 ++ .../src/korlibs/ffi/api/TestMathFFI.kt | 3 ++ .../test/korlibs/ffi/api/FFIGenerationTest.kt | 13 +++++- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 5655d03..5a1ec2d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ $catalog.json /stagedRepositoryId /*/build .DS_Store +TEST.BIN diff --git a/korlibs-ffi-ksp/src@jvm/korlibs/ffi/ksp/FFIBuilderProcessor.kt b/korlibs-ffi-ksp/src@jvm/korlibs/ffi/ksp/FFIBuilderProcessor.kt index 84ab636..6e74014 100644 --- a/korlibs-ffi-ksp/src@jvm/korlibs/ffi/ksp/FFIBuilderProcessor.kt +++ b/korlibs-ffi-ksp/src@jvm/korlibs/ffi/ksp/FFIBuilderProcessor.kt @@ -134,8 +134,9 @@ private class FFIBuilderProcessor(val environment: SymbolProcessorEnvironment) : it.appendLine("}") } isJs -> { - it.appendLine("fun DenoPointer_to_FFIPointer(v: dynamic): FFIPointer = FFIPointer(js(\"Deno.UnsafePointer.value(v)\").toString().toLong())") - it.appendLine("fun FFIPointer_to_DenoPointer(v: FFIPointer): dynamic { val vv = v.address.toString(); return js(\"Deno.UnsafePointer.create(BigInt(vv))\") }") + it.appendLine("private fun String_to_DenoPointer(str: String): dynamic = js(\"(Deno.UnsafePointer.of(new TextEncoder().encode(str)))\")") + it.appendLine("private fun DenoPointer_to_FFIPointer(v: dynamic): FFIPointer = FFIPointer(js(\"Deno.UnsafePointer.value(v)\").toString().toLong())") + it.appendLine("private fun FFIPointer_to_DenoPointer(v: FFIPointer): dynamic { val vv = v.address.toString(); return js(\"Deno.UnsafePointer.create(BigInt(vv))\") }") it.appendLine("private fun __load_$classNameImpl() = js(\"\"\"") it.appendLine(" (typeof Deno === 'undefined') ? {} : Deno.dlopen(Deno.build.os === 'windows' ? '$libraryNameWin' : Deno.build.os === 'darwin' ? '$libraryNameMac' : '$libraryNameLinux', {") @@ -178,8 +179,19 @@ private class FFIBuilderProcessor(val environment: SymbolProcessorEnvironment) : val defaultCasts = object : PlatformCasts {} val denoCasts = object : PlatformCasts { - override fun cast(str: String, type: KSTypeReference?): String = type.asString().let { if (it == "FFIPointer") "DenoPointer_to_FFIPointer($str)" else str } - override fun revCast(str: String, type: KSTypeReference?): String = type.asString().let { if (it == "FFIPointer") "FFIPointer_to_DenoPointer($str)" else str } + override fun cast(str: String, type: KSTypeReference?): String = type.asString().let { + when (it) { + "FFIPointer" -> "DenoPointer_to_FFIPointer($str)" + else -> str + } + } + override fun revCast(str: String, type: KSTypeReference?): String = type.asString().let { + when (it) { + "FFIPointer" -> "FFIPointer_to_DenoPointer($str)" + "String" -> "String_to_DenoPointer($str)" + else -> str + } + } } val jnaCasts = object : PlatformCasts { override fun typeProcessor(type: KSTypeReference?): String = type.asString().let { @@ -193,9 +205,26 @@ private class FFIBuilderProcessor(val environment: SymbolProcessorEnvironment) : override fun revCast(str: String, type: KSTypeReference?): String = type.asString().let { if (it == "FFIPointer") "$str.toPointer()" else str } } val knativeCasts = object : PlatformCasts { - override fun typeProcessor(type: KSTypeReference?): String = type.asString().let { if (it == "FFIPointer") "COpaquePointer?" else it } - override fun cast(str: String, type: KSTypeReference?): String = type.asString().let { if (it == "FFIPointer") "$str.toFFIPointer()" else str } - override fun revCast(str: String, type: KSTypeReference?): String = type.asString().let { if (it == "FFIPointer") "$str.toPointer()" else str } + override fun typeProcessor(type: KSTypeReference?): String = type.asString().let { + when (it) { + "FFIPointer" -> "COpaquePointer?" + "String" -> "CValues" + else -> it + } + } + override fun cast(str: String, type: KSTypeReference?): String = type.asString().let { + when (it) { + "FFIPointer" -> "$str.toFFIPointer()" + else -> str + } + } + override fun revCast(str: String, type: KSTypeReference?): String = type.asString().let { + when (it) { + "FFIPointer" -> "$str.toPointer()" + "String" -> "$str.cstr" + else -> str + } + } } } diff --git a/korlibs-ffi/src/korlibs/ffi/api/FFI.kt b/korlibs-ffi/src/korlibs/ffi/api/FFI.kt index 9db69a6..2134050 100644 --- a/korlibs-ffi/src/korlibs/ffi/api/FFI.kt +++ b/korlibs-ffi/src/korlibs/ffi/api/FFI.kt @@ -12,6 +12,9 @@ annotation class FFINativeInt annotation class FFIWideString inline class FFIPointer(val address: Long) { + companion object { + val NULL = FFIPointer(0L) + } @OptIn(ExperimentalStdlibApi::class) override fun toString(): String = "FFIPointer(address=0x${address.toHexString()})" } diff --git a/korlibs-ffi/src/korlibs/ffi/api/TestMathFFI.kt b/korlibs-ffi/src/korlibs/ffi/api/TestMathFFI.kt index 8bba912..4cbd8c4 100644 --- a/korlibs-ffi/src/korlibs/ffi/api/TestMathFFI.kt +++ b/korlibs-ffi/src/korlibs/ffi/api/TestMathFFI.kt @@ -8,6 +8,9 @@ internal interface TestMathFFI : AutoCloseable { //fun qsort(base: FFIPointer, number: Int, width: Int, compare: FFIFunctionRef<(FFIPointer, FFIPointer) -> Int>) //fun puts(str: String): Int //fun fputs(str: String, file: FFIPointer): Int + fun fopen(file: String, mode: String): FFIPointer + fun fclose(ptr: FFIPointer) + fun remove(file: String) companion object : TestMathFFI by TestMathFFI() { operator fun invoke(): TestMathFFI = TODO() diff --git a/korlibs-ffi/test/korlibs/ffi/api/FFIGenerationTest.kt b/korlibs-ffi/test/korlibs/ffi/api/FFIGenerationTest.kt index 95303cb..5f9d195 100644 --- a/korlibs-ffi/test/korlibs/ffi/api/FFIGenerationTest.kt +++ b/korlibs-ffi/test/korlibs/ffi/api/FFIGenerationTest.kt @@ -5,13 +5,24 @@ import kotlin.test.* class FFIGenerationTest { @Test fun test() { - if (!isSupportedFFI) return + if (!isSupportedFFI) { + println("Skipping FFIGenerationTest.test since FFI is not supported in this target") + return + } TestMathFFI().use { lib -> assertEquals(1f, lib.cosf(0f)) //lib.puts("hello world\r\n") val ptr = lib.malloc(40) //lib.qsort(ptr, 10, 4, FFIFunctionRef { l, r -> 0 }) + + lib.fopen("TEST.BIN", "wb").also { file -> + if (file != FFIPointer.NULL) { + lib.fclose(file) + } + } + lib.remove("TEST.BIN") + println(ptr) lib.free(ptr) println("AFTER FREE: $ptr")