Implement CValuesRef in interop
* Represent pointer parameters of C functions as `CValuesRef`. * Represent struct value parameters as `CValue`. * Implement some helper methods to make the new approach more useful. * Migrate some code to new interop approach for pointers and values. Also do not map to `String?`: - pointers to 8-bit integers; - pointers to non-const chars.
This commit is contained in:
committed by
SvyatoslavScherbina
parent
7a876a3ccd
commit
c9d8d4d57d
@@ -60,6 +60,8 @@ object nativeMemUtils {
|
||||
unsafe.copyMemory(source, baseOffset, null, dest.address, length.toLong())
|
||||
}
|
||||
|
||||
fun zeroMemory(dest: NativePointed, length: Int): Unit = unsafe.setMemory(dest.address, length.toLong(), 0)
|
||||
|
||||
internal class NativeAllocated(override val rawPtr: NativePtr) : NativePointed
|
||||
|
||||
fun alloc(size: Long, align: Int): NativePointed {
|
||||
|
||||
@@ -33,10 +33,49 @@ inline fun <reified T : NativePointed> NativePointed.reinterpret(): T = interpre
|
||||
*/
|
||||
interface CPointed : NativePointed
|
||||
|
||||
/**
|
||||
* Represents a reference to (possibly empty) sequence of C values.
|
||||
* It can be either a stable pointer [CPointer] or a sequence of immutable values [CValues].
|
||||
*
|
||||
* [CValuesRef] is designed to be used as Kotlin representation of pointer-typed parameters of C functions.
|
||||
* When passing [CPointer] as [CValuesRef] to the Kotlin binding method, the C function receives exactly this pointer.
|
||||
* Passing [CValues] has nearly the same semantics as passing by value: the C function receives
|
||||
* the pointer to the temporary copy of these values, and the caller can't observe the modifications to this copy.
|
||||
* The copy is valid until the C function returns.
|
||||
*/
|
||||
abstract class CValuesRef<T : CPointed> {
|
||||
/**
|
||||
* If this reference is [CPointer], returns this pointer.
|
||||
* Otherwise copies the referenced values to [placement] and returns the pointer to the copy.
|
||||
*/
|
||||
abstract fun getPointer(placement: NativePlacement): CPointer<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* The (possibly empty) sequence of immutable C values.
|
||||
* It is self-contained and doesn't depend on native memory.
|
||||
*/
|
||||
abstract class CValues<T : CVariable> : CValuesRef<T>() {
|
||||
/**
|
||||
* Copies the values to [placement] and returns the pointer to the copy.
|
||||
*/
|
||||
override abstract fun getPointer(placement: NativePlacement): CPointer<T>
|
||||
}
|
||||
|
||||
fun <T : CVariable> CValues<T>.placeTo(placement: NativePlacement) = this.getPointer(placement)
|
||||
|
||||
/**
|
||||
* The single immutable C value.
|
||||
* It is self-contained and doesn't depend on native memory.
|
||||
*
|
||||
* TODO: consider providing an adapter instead of subtyping [CValues].
|
||||
*/
|
||||
abstract class CValue<T : CVariable> : CValues<T>()
|
||||
|
||||
/**
|
||||
* C pointer.
|
||||
*/
|
||||
class CPointer<T : CPointed> internal constructor(val rawValue: NativePtr) {
|
||||
class CPointer<T : CPointed> internal constructor(val rawValue: NativePtr) : CValuesRef<T>() {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
@@ -51,6 +90,8 @@ class CPointer<T : CPointed> internal constructor(val rawValue: NativePtr) {
|
||||
}
|
||||
|
||||
override fun toString() = this.cPointerToString()
|
||||
|
||||
override fun getPointer(placement: NativePlacement) = this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +110,7 @@ inline val <reified T : CPointed> CPointer<T>.pointed: T
|
||||
|
||||
// `null` value of `CPointer?` is mapped to `nativeNullPtr`
|
||||
val CPointer<*>?.rawValue: NativePtr
|
||||
get() = this?.rawValue ?: nativeNullPtr
|
||||
get() = if (this != null) this.rawValue else nativeNullPtr
|
||||
|
||||
fun <T : CPointed> CPointer<*>.reinterpret(): CPointer<T> = interpretCPointer(this.rawValue)!!
|
||||
|
||||
@@ -217,7 +258,7 @@ typealias CPointerVar<T> = CPointerVarWithValueMappedTo<CPointer<T>>
|
||||
/**
|
||||
* The value of this variable.
|
||||
*/
|
||||
inline var <reified P : CPointer<*>> CPointerVarWithValueMappedTo<P>.value: P?
|
||||
inline var <P : CPointer<*>> CPointerVarWithValueMappedTo<P>.value: P?
|
||||
get() = interpretCPointer<CPointed>(nativeMemUtils.getNativePtr(this)) as P?
|
||||
set(value) = nativeMemUtils.putNativePtr(this, value.rawValue)
|
||||
|
||||
|
||||
@@ -43,8 +43,6 @@ class Arena(private val parent: NativeFreeablePlacement = nativeHeap) : NativePl
|
||||
|
||||
}
|
||||
|
||||
fun NativePlacement.alloc(size: Int, align: Int) = alloc(size.toLong(), align)
|
||||
|
||||
/**
|
||||
* Allocates variable of given type.
|
||||
*
|
||||
@@ -138,12 +136,9 @@ inline fun <reified T : CPointer<*>>
|
||||
}
|
||||
|
||||
fun NativePlacement.allocArrayOf(elements: ByteArray): CArray<CInt8Var> {
|
||||
val res = allocArray<CInt8Var>(elements.size)
|
||||
var index = 0
|
||||
for (byte in elements) {
|
||||
res[index++].value = byte
|
||||
}
|
||||
return res
|
||||
val result = allocArray<CInt8Var>(elements.size)
|
||||
nativeMemUtils.putByteArray(elements, result, elements.size)
|
||||
return result
|
||||
}
|
||||
|
||||
fun NativePlacement.allocArrayOf(vararg elements: Float): CArray<CFloat32Var> {
|
||||
@@ -157,56 +152,139 @@ fun NativePlacement.allocArrayOf(vararg elements: Float): CArray<CFloat32Var> {
|
||||
|
||||
fun <T : CPointed> NativePlacement.allocPointerTo() = alloc<CPointerVar<T>>()
|
||||
|
||||
fun <T : CVariable> zeroValue(size: Int, align: Int): CValue<T> = object : CValue<T>() {
|
||||
override fun getPointer(placement: NativePlacement): CPointer<T> {
|
||||
val result = placement.alloc(size, align)
|
||||
nativeMemUtils.zeroMemory(result, size)
|
||||
return interpretCPointer(result.rawPtr)!!
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : CVariable> zeroValue(): CValue<T> =
|
||||
zeroValue<T>(sizeOf<T>().toInt(), alignOf<T>())
|
||||
|
||||
private fun <T : CPointed> NativePlacement.placeBytes(bytes: ByteArray, align: Int): CPointer<T> {
|
||||
val result = this.alloc(size = bytes.size, align = align)
|
||||
nativeMemUtils.putByteArray(bytes, result, bytes.size)
|
||||
return interpretCPointer(result.rawPtr)!!
|
||||
}
|
||||
|
||||
fun <T : CVariable> CPointed.readValues(size: Int, align: Int): CValues<T> {
|
||||
val bytes = ByteArray(size)
|
||||
nativeMemUtils.getByteArray(this, bytes, size)
|
||||
|
||||
return object : CValues<T>() {
|
||||
override fun getPointer(placement: NativePlacement): CPointer<T> = placement.placeBytes(bytes, align)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : CVariable> T.readValues(count: Int): CValues<T> =
|
||||
this.readValues<T>(size = count * sizeOf<T>().toInt(), align = alignOf<T>())
|
||||
|
||||
fun <T : CVariable> CPointed.readValue(size: Int, align: Int): CValue<T> {
|
||||
val bytes = ByteArray(size)
|
||||
nativeMemUtils.getByteArray(this, bytes, size)
|
||||
return object : CValue<T>() {
|
||||
override fun getPointer(placement: NativePlacement): CPointer<T> = placement.placeBytes(bytes, align)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: can't be declared as property due to possible clash with a struct field.
|
||||
// TODO: find better name.
|
||||
inline fun <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(sizeOf<T>().toInt(), alignOf<T>())
|
||||
|
||||
/**
|
||||
* The zero-terminated string.
|
||||
* Calls the [block] with temporary copy if this value as receiver.
|
||||
*/
|
||||
class CString(override val rawPtr: NativePtr) : CPointed {
|
||||
inline fun <reified T : CStructVar, R> CValue<T>.useContents(block: T.() -> R): R = memScoped {
|
||||
this@useContents.placeTo(memScope).pointed.block()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromArray(array: CArray<CInt8Var>) = array.reinterpret<CString>()
|
||||
}
|
||||
inline fun <reified T : CStructVar> CValue<T>.copy(modify: T.() -> Unit): CValue<T> = useContents {
|
||||
this.modify()
|
||||
this.readValue()
|
||||
}
|
||||
|
||||
fun length(): Int {
|
||||
val array = reinterpret<CArray<CInt8Var>>()
|
||||
inline fun <reified T : CStructVar> cValue(initialize: T.() -> Unit): CValue<T> =
|
||||
zeroValue<T>().copy(modify = initialize)
|
||||
|
||||
var res = 0
|
||||
while (array[res].value != 0.toByte()) {
|
||||
++res
|
||||
inline fun <reified T : CVariable> createValues(count: Int, initializer: T.(index: Int) -> Unit) = memScoped {
|
||||
val array = allocArray<T>(count, initializer)
|
||||
array[0].readValues(count)
|
||||
}
|
||||
|
||||
fun cValuesOf(vararg elements: Byte): CValues<CInt8Var> = object : CValues<CInt8Var>() {
|
||||
override fun getPointer(placement: NativePlacement) = placement.allocArrayOf(elements)[0].ptr
|
||||
}
|
||||
|
||||
// TODO: optimize other [cValuesOf] methods:
|
||||
|
||||
fun cValuesOf(vararg elements: Short): CValues<CInt16Var> =
|
||||
createValues(elements.size) { index -> this.value = elements[index] }
|
||||
|
||||
fun cValuesOf(vararg elements: Int): CValues<CInt32Var> =
|
||||
createValues(elements.size) { index -> this.value = elements[index] }
|
||||
|
||||
fun cValuesOf(vararg elements: Long): CValues<CInt64Var> =
|
||||
createValues(elements.size) { index -> this.value = elements[index] }
|
||||
|
||||
fun cValuesOf(vararg elements: Float): CValues<CFloat32Var> = object : CValues<CFloat32Var>() {
|
||||
override fun getPointer(placement: NativePlacement) = placement.allocArrayOf(*elements)[0].ptr
|
||||
}
|
||||
|
||||
fun cValuesOf(vararg elements: Double): CValues<CFloat64Var> =
|
||||
createValues(elements.size) { index -> this.value = elements[index] }
|
||||
|
||||
fun <T : CPointed> cValuesOf(vararg elements: CPointer<T>?): CValues<CPointerVar<T>> =
|
||||
createValues(elements.size) { index -> this.value = elements[index] }
|
||||
|
||||
fun ByteArray.toCValues() = cValuesOf(*this)
|
||||
fun ShortArray.toCValues() = cValuesOf(*this)
|
||||
fun IntArray.toCValues() = cValuesOf(*this)
|
||||
fun LongArray.toCValues() = cValuesOf(*this)
|
||||
fun FloatArray.toCValues() = cValuesOf(*this)
|
||||
fun DoubleArray.toCValues() = cValuesOf(*this)
|
||||
fun <T : CPointed> Array<CPointer<T>?>.toCValues() = cValuesOf(*this)
|
||||
|
||||
fun <T : CPointed> List<CPointer<T>?>.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<CInt8Var>
|
||||
get() {
|
||||
val bytes = encodeToUtf8(this)
|
||||
|
||||
return object : CValues<CInt8Var>() {
|
||||
override fun getPointer(placement: NativePlacement): CPointer<CInt8Var> {
|
||||
val result = placement.allocArray<CInt8Var>(bytes.size + 1)
|
||||
nativeMemUtils.putByteArray(bytes, result, bytes.size)
|
||||
result[bytes.size].value = 0.toByte()
|
||||
return result[0].ptr
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val array = reinterpret<CArray<CInt8Var>>()
|
||||
/**
|
||||
* TODO: should the name of the function reflect the encoding?
|
||||
*
|
||||
* @return the [kotlin.String] decoded from given zero-terminated UTF-8-encoded C string.
|
||||
*/
|
||||
fun CPointer<CInt8Var>.toKString(): String {
|
||||
val nativeBytes = this.reinterpret<CArray<CInt8Var>>().pointed
|
||||
|
||||
val len = this.length()
|
||||
val bytes = ByteArray(len)
|
||||
|
||||
nativeMemUtils.getByteArray(array[0], bytes, len)
|
||||
return decodeFromUtf8(bytes) // TODO: encoding
|
||||
var length = 0
|
||||
while (nativeBytes[length].value != 0.toByte()) {
|
||||
++length
|
||||
}
|
||||
|
||||
fun asCharPtr() = reinterpret<CInt8Var>().ptr
|
||||
val bytes = ByteArray(length)
|
||||
nativeMemUtils.getByteArray(nativeBytes, bytes, length)
|
||||
return decodeFromUtf8(bytes)
|
||||
}
|
||||
|
||||
fun CString.Companion.fromString(str: String?, placement: NativePlacement): CString? {
|
||||
if (str == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val bytes = encodeToUtf8(str) // TODO: encoding
|
||||
val len = bytes.size
|
||||
val nativeBytes = placement.allocArray<CInt8Var>(len + 1)
|
||||
|
||||
nativeMemUtils.putByteArray(bytes, nativeBytes[0], len)
|
||||
nativeBytes[len].value = 0
|
||||
|
||||
return CString.fromArray(nativeBytes)
|
||||
}
|
||||
|
||||
fun CPointer<CInt8Var>.asCString() = CString.fromArray(this.reinterpret<CArray<CInt8Var>>().pointed)
|
||||
fun String.toCString(placement: NativePlacement) = CString.fromString(this, placement)
|
||||
|
||||
class MemScope : NativePlacement {
|
||||
|
||||
private val arena = Arena()
|
||||
|
||||
@@ -30,6 +30,7 @@ object nativeMemUtils {
|
||||
@Intrinsic external fun getNativePtr(mem: NativePointed): NativePtr
|
||||
@Intrinsic external fun putNativePtr(mem: NativePointed, value: NativePtr)
|
||||
|
||||
// TODO: optimize
|
||||
fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) {
|
||||
val sourceArray: CArray<CInt8Var> = source.reinterpret()
|
||||
for (index in 0 .. length - 1) {
|
||||
@@ -37,6 +38,7 @@ object nativeMemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: optimize
|
||||
fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) {
|
||||
val destArray: CArray<CInt8Var> = dest.reinterpret()
|
||||
for (index in 0 .. length - 1) {
|
||||
@@ -44,6 +46,14 @@ object nativeMemUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: optimize
|
||||
fun zeroMemory(dest: NativePointed, length: Int): Unit {
|
||||
val destArray: CArray<CInt8Var> = dest.reinterpret()
|
||||
for (index in 0 .. length - 1) {
|
||||
destArray[index].value = 0
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeAllocated(override val rawPtr: NativePtr) : NativePointed
|
||||
|
||||
fun alloc(size: Long, align: Int): NativePointed {
|
||||
|
||||
Reference in New Issue
Block a user