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:
Svyatoslav Scherbina
2017-03-16 14:48:17 +07:00
committed by SvyatoslavScherbina
parent 7a876a3ccd
commit c9d8d4d57d
22 changed files with 2072 additions and 1557 deletions
@@ -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 {