The <docs> say about converting Kotlin strings to ...
# kotlin-native
j
The docs say about converting Kotlin strings to C strings:
To get the pointer,
.cstr
should be allocated in native memory, e.g.
val cString = kotlinString.cstr.getPointer(nativeHeap)
But
getPointer()
accepts an
AutofreeScope
parameter, which
nativeHeap
doesn't fulfill. Seems like it should be accepting a
NativePlacement
instead. (Maybe it used to?) Is there a more concise/idiomatic way to copy a Kotlin string to the native heap than something like this?
Copy code
val string = "my kotlin string"
val bytes = string.encodeToByteArray()
val cPtr = nativeHeap.allocArray<ByteVar>(bytes.size + 1)
bytes.usePinned {
    memcpy(cPtr, it.addressOf(0), bytes.size.convert())
}
Looks like this commit did indeed change the parameter from
NativePlacement
to
AutofreeScope
. @svyatoslav.scherbina please, could you shed some light on this?
Using the
getPointer()
code with
nativeHeap
instead of the
Autofreescope
parameter looks as if it should work:
Copy code
val string = "my kotlin string"
val cstr = string.cstr
val cPtr = cstr.place(interpretCPointer(nativeHeap.alloc(cstr.size, cstr.align).rawPtr)!!)
What is the reason this parameter type was changed?
n
If you use a memScoped block (uses Autofreescope under the covers) then the process is much easier, eg:
Copy code
// ...
val kotlinStr = "A String"

fun kotlinFunc() = memScoped {
  val cStr = kotlinStr.cstr
  val ptr = kotlinStr.cstr.ptr
  // Do something with cstr and/or ptr here.
}
Most of the time the ptr extension property is used rather than the cstr extension property, when dealing with C APIs and Strings in cases where a Kotlin String can't be passed through to a C function as is.
Now if Kotlin Native had its own conventions document then the memScoped block would be recommended for manual memory management.
j
In this particular case I'm needing to pass a c string to be retained and used by the C library code. So I need the memory to be allocated on the native heap, rather than a temporary memScope.
s
Is there a more concise/idiomatic way to copy a Kotlin string to the native heap than something like this?
Yes:
Copy code
val arena = Arena()
string.cstr.getPointer(arena)
// Somewhen later, to reclaim the memory:
arena.clear()
You can also reuse an
Arena
instance for more strings. Regarding your original example (if for some reason you would like to proceed with this approach instead):
Copy code
bytes.usePinned {
    memcpy(cPtr, it.addressOf(0), bytes.size.convert())
}
This can be simplified to
Copy code
memcpy(cPtr, bytes.refTo(0), bytes.size.convert())
You might also find
strdup
useful, instead of
memcpy
.
What is the reason this parameter type was changed?
To make
getPointer
more safe (so it is harder to accidentally allow memory leak) and more flexible (e.g. to implement `refTo`: https://github.com/JetBrains/kotlin/blob/2ba7c7b8a99626a28c0058cd1af628b588cf06a8/[…]e/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/Pinning.kt).
In this particular case I’m needing to pass a c string to be retained and used by the C library code. So I need the memory to be allocated on the native heap, rather than a temporary memScope.
Does this C library call
free
on the string later?
j
Thanks for this info. This context is very helpful. I'm in the beginning stages of exploring adding additional native targets to my multiplatform database bindings library. Currently I support JVM, Android, iOS, and macOS, utilizing the database's native JVM and ObjC SDKs. One of the challenges I'm trying to work through is how to keep the multiplatform Kotlin API consistent, while managing the underlying memory for these additional native targets that rely on manual reference counting in the C SDK. I don't want to surface the need for consumers of the multiplatform database API to scope memory access. But without a destructor/finalizer mechanism in Kotlin/Native, I need to somehow ensure the wrapped native objects are properly released when the Kotlin objects are GCed.
Does this C library call
free
on the string later?
The C library binds the ownership of the string in this case to another object from the SDK, for example an Array or Dictionary object. When the Array or Dictionary is released, it frees the underlying string(s) and other memory allocated to objects within the data structure. Users of the C library are expected to call
retain()
and
release()
on the data structure object via the library's manual reference counting API.
s
The C library binds the ownership of the string in this case to another object from the SDK, for example an Array or Dictionary object. When the Array or Dictionary is released, it frees the underlying string(s) and other memory allocated to objects within the data structure.
Ok. Then allocating C string in an
Arena
won’t work, because it can’t be properly disposed with
free
. Even
nativeHeap
doesn’t guarantee that it uses
malloc
under the hood, so whatever was allocated with
nativeHeap.alloc
should be freed with
nativeHeap.free
, not the regular C
free
. So the safest option here probably is to use
strdup
, or
malloc
+
memcpy
.
j
That's helpful to know as well. It looks like
strdup
is probably the simplest API for this string copy use case, as well as
strlen
, as I also need the string size in the struct API. After reading some of the issues around destructor/finalizer mechanisms in Kotlin/Native, I found your comment here suggesting
Cleaner
. This seems to be the best solution for ensuring wrapped native resources are released in classes that aren't
AutoCloseable
in my multiplatform API. It'd be great to have `AutoCloseable` in common stdlib as well. I've had to redefine the JVM class and Kotlin
use
extension for my multiplatform project. Looks like this will hopefully be possible in Kotlin 1.8.