From 2d6d4b15e66323a6d2188fdaa4c1e110105ea666 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Wed, 7 Jun 2017 14:23:35 +0300 Subject: [PATCH] Support for wchar, minor opts in interop runtime (#641) --- .../kotlin/kotlinx/cinterop/JvmNativeMem.kt | 12 +++++++ .../src/main/kotlin/kotlinx/cinterop/Utils.kt | 29 +++++++++++++---- .../kotlin/kotlinx/cinterop/NativeMem.kt | 32 +++++++++++++++++-- .../native/interop/gen/jvm/StubGenerator.kt | 23 +++++++++++++ samples/win32/MessageBox.kt | 6 ++++ samples/win32/build.bat | 12 +++++++ samples/win32/win32.def | 3 ++ 7 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 samples/win32/MessageBox.kt create mode 100644 samples/win32/build.bat create mode 100644 samples/win32/win32.def diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt index 6ed993d2376..2bffc0b99c3 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt @@ -76,6 +76,18 @@ object nativeMemUtils { unsafe.copyMemory(source, baseOffset, null, dest.address, length.toLong()) } + fun getCharArray(source: NativePointed, dest: CharArray, length: Int) { + val clazz = CharArray::class.java + val baseOffset = unsafe.arrayBaseOffset(clazz).toLong(); + unsafe.copyMemory(null, source.address, dest, baseOffset, length.toLong() * 2) + } + + fun putCharArray(source: CharArray, dest: NativePointed, length: Int) { + val clazz = CharArray::class.java + val baseOffset = unsafe.arrayBaseOffset(clazz).toLong(); + unsafe.copyMemory(source, baseOffset, null, dest.address, length.toLong() * 2) + } + fun zeroMemory(dest: NativePointed, length: Int): Unit = unsafe.setMemory(dest.address, length.toLong(), 0) internal class NativeAllocated(override val rawPtr: NativePtr) : NativePointed diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index cf50ed81ca3..5d5e733cca4 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -147,10 +147,11 @@ inline fun > NativePlacement.allocArrayOf(elements: List): CArrayPointer> { val res = allocArray>(elements.size) - elements.forEachIndexed { index, value -> - res[index] = value + var index = 0 + while (index < elements.size) { + res[index] = elements[index] + ++index } - return res } @@ -163,8 +164,9 @@ fun NativePlacement.allocArrayOf(elements: ByteArray): CArrayPointer { fun NativePlacement.allocArrayOf(vararg elements: Float): CArrayPointer { val res = allocArray(elements.size) var index = 0 - for (element in elements) { - res[index++] = element + while (index < elements.size) { + res[index] = elements[index] + ++index } return res } @@ -287,8 +289,6 @@ fun Array?>.toCValues() = cValuesOf(*this) fun List?>.toCValues() = this.toTypedArray().toCValues() /** - * TODO: should the name of the function reflect the encoding? - * * @return the value of zero-terminated UTF-8-encoded C string constructed from given [kotlin.String]. */ val String.cstr: CValues @@ -307,6 +307,21 @@ val String.cstr: CValues } } +val String.wcstr: CValues + get() { + val chars = CharArray(this.length, { i -> this.get(i)}) + return object : CValues() { + override val size get() = chars.size + 1 + + override fun getPointer(placement: NativePlacement): CPointer { + val result = placement.allocArray(chars.size + 1) + nativeMemUtils.putCharArray(chars, result.pointed, chars.size) + result[chars.size] = 0.toShort() + return result + } + } + } + /** * TODO: should the name of the function reflect the encoding? * diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt index 7b11b00d4ae..43b9210099f 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt @@ -33,24 +33,50 @@ object nativeMemUtils { // TODO: optimize fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) { val sourceArray = source.reinterpret().ptr - for (index in 0 .. length - 1) { + var index = 0 + while (index < length) { dest[index] = sourceArray[index] + ++index } } // TODO: optimize fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) { val destArray = dest.reinterpret().ptr - for (index in 0 .. length - 1) { + var index = 0 + while (index < length) { destArray[index] = source[index] + ++index + } + } + + // TODO: optimize + fun getCharArray(source: NativePointed, dest: CharArray, length: Int) { + val sourceArray = source.reinterpret().ptr + var index = 0 + while (index < length) { + dest[index] = sourceArray[index].toChar() + ++index + } + } + + // TODO: optimize + fun putCharArray(source: CharArray, dest: NativePointed, length: Int) { + val destArray = dest.reinterpret().ptr + var index = 0 + while (index < length) { + destArray[index] = source[index].toShort() + ++index } } // TODO: optimize fun zeroMemory(dest: NativePointed, length: Int): Unit { val destArray = dest.reinterpret().ptr - for (index in 0 .. length - 1) { + var index = 0 + while (index < length) { destArray[index] = 0 + ++index } } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 0da4241fc25..3842aa250da 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -60,6 +60,8 @@ class StubGenerator( "true", "try", "typealias", "val", "var", "when", "while" ) + val platformWStringTypes = setOf("LPCWSTR") + /** * For this identifier constructs the string to be parsed by Kotlin as `SimpleName` * defined [here](https://kotlinlang.org/docs/reference/grammar.html#SimpleName). @@ -516,12 +518,24 @@ class StubGenerator( return pointeeType } + private fun Type.isAliasOf(names: Set): Boolean { + var type = this + while (type is Typedef) { + if (names.contains(type.def.name)) return true + type = type.def.aliased + } + return false + } + fun representCFunctionParameterAsString(type: Type): Boolean { val unwrappedType = type.unwrapTypedefs() return unwrappedType is PointerType && unwrappedType.pointeeIsConst && unwrappedType.pointeeType.unwrapTypedefs() == CharType } + // We take this approach as generic 'const short*' shall not be used as String. + fun representCFunctionParameterAsWString(type: Type)= type.isAliasOf(platformWStringTypes) + fun getCFunctionParamBinding(type: Type): OutValueBinding { if (representCFunctionParameterAsString(type)) { return OutValueBinding( @@ -532,6 +546,15 @@ class StubGenerator( ) } + if (representCFunctionParameterAsWString(type)) { + return OutValueBinding( + kotlinType = "String?", // TODO: mention the C type (e.g. with annotation). + kotlinConv = { name -> "$name?.wcstr?.getPointer(memScope).rawValue" }, + memScoped = true, + kotlinJniBridgeType = "NativePtr" + ) + } + representCFunctionParameterAsValuesRef(type)?.let { val pointeeMirror = mirror(it) return OutValueBinding( diff --git a/samples/win32/MessageBox.kt b/samples/win32/MessageBox.kt new file mode 100644 index 00000000000..9109bbeed9e --- /dev/null +++ b/samples/win32/MessageBox.kt @@ -0,0 +1,6 @@ +import win32.* + +fun main(args: Array) { + MessageBoxW(null, "Konan говорит:\nЗДРАВСТВУЙ МИР!\n", + "Заголовок окна", MB_YESNOCANCEL or MB_ICONQUESTION) +} \ No newline at end of file diff --git a/samples/win32/build.bat b/samples/win32/build.bat new file mode 100644 index 00000000000..27e05030ac2 --- /dev/null +++ b/samples/win32/build.bat @@ -0,0 +1,12 @@ +@echo off +setlocal +set DIR=. +set "PATH=..\..\dist\bin;..\..\bin;%PATH%" +if "%TARGET%" == "" set TARGET=mingw + +set "LFLAGS=-Wl,--subsystem,windows" + +call cinterop -def "%DIR%\win32.def" -target "%TARGET%" -o win32 || exit /b +call konanc -target "%TARGET%" "%DIR%\MessageBox.kt" -library win32 -linkerOpts "%LFLAGS%" -opt -o MessageBox || exit /b + +copy MessageBox.kexe MessageBox.exe diff --git a/samples/win32/win32.def b/samples/win32/win32.def new file mode 100644 index 00000000000..f616c5eda48 --- /dev/null +++ b/samples/win32/win32.def @@ -0,0 +1,3 @@ +headers = windows.h +headerFilter = win**.h +compilerOpts = -DUNICODE -Wno-incompatible-pointer-types -Wno-deprecated-declarations \ No newline at end of file