Interop: implement new version
This commit is contained in:
@@ -37,8 +37,11 @@ model {
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
}
|
||||
|
||||
sourceSets.main.kotlin.srcDirs += "src/jvm/kotlin"
|
||||
|
||||
|
||||
task nativelibs(type: Copy) {
|
||||
dependsOn 'callbacksSharedLibrary'
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package kotlin_.cinterop
|
||||
|
||||
/**
|
||||
* This class provides a way to create a stable handle to any Kotlin object.
|
||||
* Its [value] can be safely passed to native code e.g. to be received in a Kotlin callback.
|
||||
*
|
||||
* Any [StableObjPtr] should be manually [disposed][dispose]
|
||||
*/
|
||||
data class StableObjPtr private constructor(val value: COpaquePointer) {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Creates a handle for given object.
|
||||
*/
|
||||
fun create(any: Any) = fromValue(newGlobalRef(any))
|
||||
|
||||
private fun fromValue(value: NativePtr) = fromValue(CPointer.create(value))
|
||||
|
||||
/**
|
||||
* Creates [StableObjPtr] from given raw value.
|
||||
*
|
||||
* @param value must be a [value] of some [StableObjPtr]
|
||||
*/
|
||||
fun fromValue(value: COpaquePointer) = StableObjPtr(value)
|
||||
|
||||
init {
|
||||
loadCallbacksLibrary()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the handle. It must not be [used][get] after that.
|
||||
*/
|
||||
fun dispose() {
|
||||
deleteGlobalRef(value.rawValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object this handle was [created][create] for.
|
||||
*/
|
||||
fun get(): Any = derefGlobalRef(value.rawValue)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the type of C function with adapter for Kotlin functions.
|
||||
*
|
||||
* The instances of this class are supposed to be Kotlin object declarations (singletons),
|
||||
* because it is required by [CAdaptedFunctionType] and
|
||||
* because creating the instance implies allocating some amount of non-freeable memory for the instance itself
|
||||
* and for any unique Kotlin function "converted" to this type.
|
||||
*
|
||||
* Native function type definition consists in the following:
|
||||
* - Definitions of native function's parameter and return types to be passed into the constructor
|
||||
* - Implementation of [invoke] method which describes how to convert between these types and Kotlin types used in [F]
|
||||
*
|
||||
* @param F Kotlin function type corresponding to given native function type
|
||||
*/
|
||||
abstract class CAdaptedFunctionTypeImpl<F : Function<*>>
|
||||
protected constructor(returnType: CType, vararg paramTypes: CType) : CAdaptedFunctionType<F> {
|
||||
|
||||
override fun fromStatic(function: F): NativePtr {
|
||||
// TODO: optimize synchronization
|
||||
synchronized(cache) {
|
||||
return cache.getOrPut(function, { createFromStatic(function) })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the C type of a function's parameter or return value.
|
||||
* It is supposed to be constructed using the primitive types (such as [SInt32]) and the [Struct] combinator.
|
||||
*
|
||||
* This description omits the details that are irrelevant for the ABI.
|
||||
*/
|
||||
protected open class CType internal constructor(val ffiType: ffi_type) {
|
||||
internal constructor(ffiTypePtr: Long) : this(interpretPointed<ffi_type>(ffiTypePtr))
|
||||
}
|
||||
|
||||
protected object Void : CType(ffiTypeVoid())
|
||||
protected object UInt8 : CType(ffiTypeUInt8())
|
||||
protected object SInt8 : CType(ffiTypeSInt8())
|
||||
protected object UInt16 : CType(ffiTypeUInt16())
|
||||
protected object SInt16 : CType(ffiTypeSInt16())
|
||||
protected object UInt32 : CType(ffiTypeUInt32())
|
||||
protected object SInt32 : CType(ffiTypeSInt32())
|
||||
protected object UInt64 : CType(ffiTypeUInt64())
|
||||
protected object SInt64 : CType(ffiTypeSInt64())
|
||||
protected object Pointer : CType(ffiTypePointer())
|
||||
|
||||
protected class Struct(vararg elementTypes: CType) : CType(
|
||||
ffiTypeStruct(
|
||||
elementTypes.map { it.ffiType }
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* This method should invoke given Kotlin function.
|
||||
*
|
||||
* @param args array of pointers to arguments to be passed to [function]
|
||||
* @param ret pointer to memory to be filled with return value of [function]
|
||||
*/
|
||||
protected abstract fun invoke(function: F, args: CArray<COpaquePointerVar>, ret: COpaquePointer)
|
||||
|
||||
companion object {
|
||||
init {
|
||||
loadCallbacksLibrary()
|
||||
}
|
||||
}
|
||||
|
||||
private val ffiCif = ffiCreateCif(returnType.ffiType, paramTypes.map { it.ffiType })
|
||||
|
||||
/**
|
||||
* Allocates a native function of this type for given Kotlin function.
|
||||
*/
|
||||
private fun createFromStatic(function: F): NativePtr {
|
||||
if (!isStatic(function)) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val impl: UserData = { ret: COpaquePointer, args: CArray<COpaquePointerVar> ->
|
||||
invoke(function, args, ret)
|
||||
}
|
||||
|
||||
return ffiCreateClosure(ffiCif, impl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if given function is *static* as defined in [fromStatic].
|
||||
*/
|
||||
private fun isStatic(function: Function<*>): Boolean {
|
||||
// TODO: revise
|
||||
try {
|
||||
with(function.javaClass.getDeclaredField("INSTANCE")) {
|
||||
if (!java.lang.reflect.Modifier.isStatic(modifiers) || !java.lang.reflect.Modifier.isFinal(modifiers)) {
|
||||
return false
|
||||
}
|
||||
|
||||
isAccessible = true // TODO: undo
|
||||
|
||||
return get(null) == function
|
||||
}
|
||||
} catch (e: NoSuchFieldException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private val cache = mutableMapOf<F, NativePtr>()
|
||||
}
|
||||
|
||||
private typealias UserData = (ret: COpaquePointer, args: CArray<COpaquePointerVar>)->Unit
|
||||
|
||||
inline fun <reified T : CAdaptedFunctionTypeImpl<*>> CAdaptedFunctionTypeImpl.Companion.of(): T =
|
||||
T::class.objectInstance!!
|
||||
|
||||
private fun loadCallbacksLibrary() {
|
||||
System.loadLibrary("callbacks")
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reference to `ffi_type` struct instance.
|
||||
*/
|
||||
internal class ffi_type(override val rawPtr: NativePtr) : COpaque
|
||||
|
||||
/**
|
||||
* Reference to `ffi_cif` struct instance.
|
||||
*/
|
||||
internal class ffi_cif(override val rawPtr: NativePtr) : COpaque
|
||||
|
||||
private external fun ffiTypeVoid(): Long
|
||||
private external fun ffiTypeUInt8(): Long
|
||||
private external fun ffiTypeSInt8(): Long
|
||||
private external fun ffiTypeUInt16(): Long
|
||||
private external fun ffiTypeSInt16(): Long
|
||||
private external fun ffiTypeUInt32(): Long
|
||||
private external fun ffiTypeSInt32(): Long
|
||||
private external fun ffiTypeUInt64(): Long
|
||||
private external fun ffiTypeSInt64(): Long
|
||||
private external fun ffiTypePointer(): Long
|
||||
|
||||
private external fun ffiTypeStruct0(elements: Long): Long
|
||||
|
||||
/**
|
||||
* Allocates and initializes `ffi_type` describing the struct.
|
||||
*
|
||||
* @param elements types of the struct elements
|
||||
*/
|
||||
private fun ffiTypeStruct(elementTypes: List<ffi_type>): ffi_type {
|
||||
val elements = nativeHeap.allocArrayOfPointersTo(*elementTypes.toTypedArray(), null)
|
||||
val res = ffiTypeStruct0(elements.rawPtr)
|
||||
if (res == 0L) {
|
||||
throw OutOfMemoryError()
|
||||
}
|
||||
return interpretPointed(res)
|
||||
}
|
||||
|
||||
private external fun ffiCreateCif0(nArgs: Int, rType: Long, argTypes: Long): Long
|
||||
|
||||
/**
|
||||
* Creates and prepares an `ffi_cif`.
|
||||
*
|
||||
* @param returnType native function return value type
|
||||
* @param paramTypes native function parameter types
|
||||
*
|
||||
* @return the initialized `ffi_cif`
|
||||
*/
|
||||
private fun ffiCreateCif(returnType: ffi_type, paramTypes: List<ffi_type>): ffi_cif {
|
||||
val nArgs = paramTypes.size
|
||||
val argTypes = nativeHeap.allocArrayOfPointersTo(*paramTypes.toTypedArray(), null)
|
||||
val res = ffiCreateCif0(nArgs, returnType.rawPtr, argTypes.rawPtr)
|
||||
|
||||
when (res) {
|
||||
0L -> throw OutOfMemoryError()
|
||||
-1L -> throw Error("FFI_BAD_TYPEDEF")
|
||||
-2L -> throw Error("FFI_BAD_ABI")
|
||||
-3L -> throw Error("libffi error occurred")
|
||||
}
|
||||
|
||||
return interpretPointed(res)
|
||||
}
|
||||
|
||||
private fun ffiFunImpl0(ffiCif: Long, ret: Long, args: Long, userData: Any) {
|
||||
ffiFunImpl(interpretPointed(ffiCif),
|
||||
CPointer.create(ret),
|
||||
interpretPointed(args),
|
||||
userData as UserData)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called from native code when a native function created with [ffiCreateClosure] is invoked.
|
||||
*
|
||||
* @param ret pointer to memory to be filled with return value of the invoked native function
|
||||
* @param args pointer to array of pointers to arguments passed to the invoked native function
|
||||
*/
|
||||
private fun ffiFunImpl(ffiCif: ffi_cif, ret: COpaquePointer, args: CArray<COpaquePointerVar>,
|
||||
userData: UserData) {
|
||||
|
||||
userData.invoke(ret, args)
|
||||
}
|
||||
|
||||
private external fun ffiCreateClosure0(ffiCif: Long, userData: Any): Long
|
||||
|
||||
/**
|
||||
* Uses libffi to allocate a native function which will call [ffiFunImpl] when invoked.
|
||||
*
|
||||
* @param ffiCif describes the type of the function to create
|
||||
*/
|
||||
private fun ffiCreateClosure(ffiCif: ffi_cif, userData: UserData): NativePtr {
|
||||
val res = ffiCreateClosure0(ffiCif.rawPtr, userData)
|
||||
|
||||
when (res) {
|
||||
0L -> throw OutOfMemoryError()
|
||||
-1L -> throw Error("libffi error occurred")
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private external fun newGlobalRef(any: Any): Long
|
||||
private external fun derefGlobalRef(ref: Long): Any
|
||||
private external fun deleteGlobalRef(ref: Long)
|
||||
@@ -0,0 +1,67 @@
|
||||
package kotlin_.cinterop
|
||||
|
||||
import sun.misc.Unsafe
|
||||
|
||||
private val NativePointed.address: Long
|
||||
get() = this.rawPtr
|
||||
|
||||
private enum class DataModel(val pointerSize: Long) {
|
||||
_32BIT(4),
|
||||
_64BIT(8)
|
||||
}
|
||||
|
||||
private val dataModel: DataModel = when (System.getProperty("sun.arch.data.model")) {
|
||||
null -> TODO()
|
||||
"32" -> DataModel._32BIT
|
||||
"64" -> DataModel._64BIT
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
|
||||
internal val pointerSize: Int = dataModel.pointerSize.toInt()
|
||||
|
||||
object nativeMemUtils {
|
||||
fun getByte(mem: NativePointed) = unsafe.getByte(mem.address)
|
||||
fun putByte(mem: NativePointed, value: Byte) = unsafe.putByte(mem.address, value)
|
||||
|
||||
fun getShort(mem: NativePointed) = unsafe.getShort(mem.address)
|
||||
fun putShort(mem: NativePointed, value: Short) = unsafe.putShort(mem.address, value)
|
||||
|
||||
fun getInt(mem: NativePointed) = unsafe.getInt(mem.address)
|
||||
fun putInt(mem: NativePointed, value: Int) = unsafe.putInt(mem.address, value)
|
||||
|
||||
fun getLong(mem: NativePointed) = unsafe.getLong(mem.address)
|
||||
fun putLong(mem: NativePointed, value: Long) = unsafe.putLong(mem.address, value)
|
||||
|
||||
fun getFloat(mem: NativePointed) = unsafe.getFloat(mem.address)
|
||||
fun putFloat(mem: NativePointed, value: Float) = unsafe.putFloat(mem.address, value)
|
||||
|
||||
fun getDouble(mem: NativePointed) = unsafe.getDouble(mem.address)
|
||||
fun putDouble(mem: NativePointed, value: Double) = unsafe.putDouble(mem.address, value)
|
||||
|
||||
fun getPtr(mem: NativePointed): NativePtr = when (dataModel) {
|
||||
DataModel._32BIT -> getInt(mem).toLong()
|
||||
DataModel._64BIT -> getLong(mem)
|
||||
}
|
||||
|
||||
fun putPtr(mem: NativePointed, value: NativePtr) = when (dataModel) {
|
||||
DataModel._32BIT -> putInt(mem, value.toInt())
|
||||
DataModel._64BIT -> putLong(mem, value)
|
||||
}
|
||||
|
||||
internal class NativeAllocated(override val rawPtr: NativePtr) : NativePointed
|
||||
|
||||
fun alloc(size: Long, align: Int): NativePointed {
|
||||
val address = unsafe.allocateMemory(size)
|
||||
if (address % align != 0L) TODO(align.toString())
|
||||
return interpretPointed<NativeAllocated>(address)
|
||||
}
|
||||
|
||||
fun free(mem: NativePointed) {
|
||||
unsafe.freeMemory(mem.rawPtr)
|
||||
}
|
||||
|
||||
private val unsafe = with(Unsafe::class.java.getDeclaredField("theUnsafe")) {
|
||||
isAccessible = true
|
||||
return@with this.get(null) as Unsafe
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package kotlin_.cinterop
|
||||
|
||||
import kotlin.reflect.companionObjectInstance
|
||||
import kotlin.reflect.primaryConstructor
|
||||
|
||||
typealias NativePtr = Long
|
||||
val nativeNullPtr: NativePtr = 0L
|
||||
|
||||
// TODO: the functions below should eventually be intrinsified
|
||||
|
||||
inline fun <reified T : CVariable> CVariable.Type.Companion.of() = T::class.companionObjectInstance as CVariable.Type
|
||||
|
||||
/**
|
||||
* Returns interpretation of entity with given pointer.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline fun <reified T : NativePointed> interpretPointed(ptr: NativePtr): T {
|
||||
return ensuringNotNull(ptr) {
|
||||
val kClass = T::class
|
||||
val primaryConstructor = kClass.primaryConstructor
|
||||
if (primaryConstructor == null) {
|
||||
throw IllegalArgumentException("${kClass.simpleName} doesn't have a constructor")
|
||||
}
|
||||
(primaryConstructor as (NativePtr) -> T)(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : CAdaptedFunctionType<*>> CAdaptedFunctionType.Companion.getInstanceOf(): T =
|
||||
T::class.objectInstance!!
|
||||
@@ -0,0 +1,331 @@
|
||||
package kotlin_.cinterop
|
||||
|
||||
/**
|
||||
* The entity which has an associated native pointer.
|
||||
* Subtypes are supposed to represent interpretations of the pointed data or code.
|
||||
*
|
||||
* This interface is likely to be handled by compiler magic and shouldn't be subtyped by arbitrary classes.
|
||||
*/
|
||||
interface NativePointed {
|
||||
val rawPtr: NativePtr
|
||||
}
|
||||
|
||||
// `null` value of `NativePointed?` is mapped to `nativeNullPtr`.
|
||||
val NativePointed?.rawPtr: NativePtr
|
||||
get() = this?.rawPtr ?: nativeNullPtr
|
||||
|
||||
/**
|
||||
* Returns interpretation of entity with given pointer, or `null` if it is null.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline fun <reified T : NativePointed> interpretNullablePointed(ptr: NativePtr): T? {
|
||||
return ifNotNull(ptr) {
|
||||
interpretPointed<T>(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the function to the pointer if it is not null, otherwise returns `null`.
|
||||
*/
|
||||
inline fun <T> ifNotNull(ptr: NativePtr, function: (NativePtr)->T): T? {
|
||||
return if (ptr == nativeNullPtr) {
|
||||
null
|
||||
} else {
|
||||
function(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the function to the pointer ensuring that it is not null.
|
||||
*/
|
||||
inline fun <T> ensuringNotNull(ptr: NativePtr, function: (NativePtr)->T): T {
|
||||
if (ptr == nativeNullPtr) {
|
||||
throw IllegalArgumentException()
|
||||
} else {
|
||||
return function(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the interpretation of the pointed data or code.
|
||||
*/
|
||||
inline fun <reified T : NativePointed> NativePointed.reinterpret(): T = interpretPointed(this.rawPtr)
|
||||
|
||||
/**
|
||||
* C data or code.
|
||||
*/
|
||||
interface CPointed : NativePointed
|
||||
|
||||
/**
|
||||
* C pointer.
|
||||
*/
|
||||
class CPointer<T : CPointed> private constructor(val rawValue: NativePtr) {
|
||||
companion object {
|
||||
fun <T : CPointed> create(rawValue: NativePtr) = ensuringNotNull(rawValue) {
|
||||
CPointer<T>(rawValue)
|
||||
}
|
||||
|
||||
fun <T : CPointed> createNullable(rawValue: NativePtr) = ifNotNull(rawValue) {
|
||||
CPointer<T>(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true // fast path
|
||||
}
|
||||
|
||||
return (other is CPointer<*>) && (rawValue == other.rawValue)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return rawValue.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val hex = "%x".format(rawValue)
|
||||
return "CPointer(raw=0x$hex)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pointer to this data or code.
|
||||
*/
|
||||
val <T : CPointed> T.ptr: CPointer<T>
|
||||
get() = CPointer.create(this.rawPtr)
|
||||
|
||||
/**
|
||||
* Returns the corresponding [CPointed].
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline val <reified T : CPointed> CPointer<T>.pointed: T
|
||||
get() = interpretPointed<T>(this.rawValue)
|
||||
|
||||
// `null` value of `CPointer?` is mapped to `nativeNullPtr`
|
||||
val CPointer<*>?.rawValue: NativePtr
|
||||
get() = this?.rawValue ?: nativeNullPtr
|
||||
|
||||
fun <T : CPointed> CPointer<*>.reinterpret() = this as CPointer<T>
|
||||
|
||||
/**
|
||||
* The [CPointed] without any specified interpretation.
|
||||
*/
|
||||
interface COpaque : CPointed // TODO: should it correspond to COpaquePointer?
|
||||
|
||||
/**
|
||||
* The pointer with an opaque type.
|
||||
*/
|
||||
typealias COpaquePointer = CPointer<out CPointed> // FIXME
|
||||
|
||||
/**
|
||||
* The variable containing a [COpaquePointer].
|
||||
*/
|
||||
typealias COpaquePointerVar = CPointerVarWithValueMappedTo<COpaquePointer>
|
||||
|
||||
/**
|
||||
* The C data variable located in memory.
|
||||
*
|
||||
* The non-abstract subclasses should represent the (complete) C data type and thus specify size and alignment.
|
||||
* Each such subclass must have a companion object which is a [Type].
|
||||
*/
|
||||
interface CVariable : CPointed {
|
||||
|
||||
/**
|
||||
* The (complete) C data type.
|
||||
*
|
||||
* @param size the size in bytes of data of this type
|
||||
* @param align the alignments in bytes that is enough for this data type.
|
||||
* It may be greater than actually required for simplicity.
|
||||
*/
|
||||
open class Type(val size: Long, val align: Int) {
|
||||
|
||||
init {
|
||||
assert (size % align == 0L)
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
companion object {
|
||||
inline fun <reified T : CVariable> sizeOf() = Type.of<T>().size
|
||||
inline fun <reified T : CVariable> alignOf() = Type.of<T>().align
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The C data which is composed from number of members.
|
||||
*/
|
||||
interface CAggregate : CPointed
|
||||
|
||||
/**
|
||||
* Returns the member of this [CAggregate] which is located by given offset in bytes.
|
||||
*/
|
||||
inline fun <reified T : CPointed> CAggregate.memberAt(offset: Long): T {
|
||||
return interpretPointed<T>(this.rawPtr + offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* The C struct-typed variable located in memory.
|
||||
*/
|
||||
abstract class CStructVar : CVariable, CAggregate {
|
||||
open class Type(size: Long, align: Int) : CVariable.Type(size, align)
|
||||
}
|
||||
|
||||
/**
|
||||
* The C primitive-typed variable located in memory.
|
||||
*/
|
||||
sealed class CPrimitiveVar : CVariable {
|
||||
// aligning by size is obviously enough
|
||||
open class Type(size: Int, align: Int = size) : CVariable.Type(size.toLong(), align)
|
||||
}
|
||||
|
||||
abstract class CEnumVar : CPrimitiveVar()
|
||||
|
||||
// generics below are used for typedef support
|
||||
// these classes are not supposed to be used directly, instead the typealiases are provided.
|
||||
|
||||
class CInt8VarWithValueMappedTo<T : Byte>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(1)
|
||||
}
|
||||
|
||||
class CInt16VarWithValueMappedTo<T : Short>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(2)
|
||||
}
|
||||
|
||||
class CInt32VarWithValueMappedTo<T : Int>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(4)
|
||||
}
|
||||
|
||||
class CInt64VarWithValueMappedTo<T : Long>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(8)
|
||||
}
|
||||
|
||||
class CFloat32VarWithValueMappedTo<T : Float>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(4)
|
||||
}
|
||||
|
||||
class CFloat64VarWithValueMappedTo<T : Double>(override val rawPtr: NativePtr) : CPrimitiveVar() {
|
||||
companion object : Type(8)
|
||||
}
|
||||
|
||||
typealias CInt8Var = CInt8VarWithValueMappedTo<Byte>
|
||||
typealias CInt16Var = CInt16VarWithValueMappedTo<Short>
|
||||
typealias CInt32Var = CInt32VarWithValueMappedTo<Int>
|
||||
typealias CInt64Var = CInt64VarWithValueMappedTo<Long>
|
||||
typealias CFloat32Var = CFloat32VarWithValueMappedTo<Float>
|
||||
typealias CFloat64Var = CFloat64VarWithValueMappedTo<Double>
|
||||
|
||||
var <T : Byte> CInt8VarWithValueMappedTo<T>.value: T
|
||||
get() = nativeMemUtils.getByte(this) as T
|
||||
set(value) = nativeMemUtils.putByte(this, value)
|
||||
|
||||
var <T : Short> CInt16VarWithValueMappedTo<T>.value: T
|
||||
get() = nativeMemUtils.getShort(this) as T
|
||||
set(value) = nativeMemUtils.putShort(this, value)
|
||||
|
||||
var <T : Int> CInt32VarWithValueMappedTo<T>.value: T
|
||||
get() = nativeMemUtils.getInt(this) as T
|
||||
set(value) = nativeMemUtils.putInt(this, value)
|
||||
|
||||
var <T : Long> CInt64VarWithValueMappedTo<T>.value: T
|
||||
get() = nativeMemUtils.getLong(this) as T
|
||||
set(value) = nativeMemUtils.putLong(this, value)
|
||||
|
||||
// TODO: ensure native floats have the appropriate binary representation
|
||||
|
||||
var <T : Float> CFloat32VarWithValueMappedTo<T>.value: T
|
||||
get() = nativeMemUtils.getFloat(this) as T
|
||||
set(value) = nativeMemUtils.putFloat(this, value)
|
||||
|
||||
var <T : Double> CFloat64VarWithValueMappedTo<T>.value: T
|
||||
get() = nativeMemUtils.getDouble(this) as T
|
||||
set(value) = nativeMemUtils.putDouble(this, value)
|
||||
|
||||
|
||||
class CPointerVarWithValueMappedTo<T : CPointer<*>>(override val rawPtr: NativePtr) : CVariable {
|
||||
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* The C data variable containing the pointer to `T`.
|
||||
*/
|
||||
typealias CPointerVar<T> = CPointerVarWithValueMappedTo<CPointer<T>>
|
||||
|
||||
/**
|
||||
* The value of this variable.
|
||||
*/
|
||||
inline var <reified P : CPointer<*>> CPointerVarWithValueMappedTo<P>.value: P?
|
||||
get() = CPointer.createNullable<CPointed>(nativeMemUtils.getPtr(this)) as P?
|
||||
set(value) = nativeMemUtils.putPtr(this, value.rawValue)
|
||||
|
||||
/**
|
||||
* The code or data pointed by the value of this variable.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline var <reified T : CPointed, reified P : CPointer<T>> CPointerVarWithValueMappedTo<P>.pointed: T?
|
||||
get() = this.value?.pointed
|
||||
set(value) {
|
||||
this.value = value?.ptr as P?
|
||||
}
|
||||
|
||||
class CArray<T : CVariable>(override val rawPtr: NativePtr) : CAggregate
|
||||
|
||||
inline fun <reified T : CVariable> CArray<T>.elementOffset(index: Long) = if (index == 0L) {
|
||||
0L // optimization for JVM impl which uses reflection for now.
|
||||
} else {
|
||||
index * CVariable.sizeOf<T>()
|
||||
}
|
||||
|
||||
inline operator fun <reified T : CVariable> CArray<T>.get(index: Long): T = memberAt(elementOffset(index))
|
||||
inline operator fun <reified T : CVariable> CArray<T>.get(index: Int) = this.get(index.toLong())
|
||||
|
||||
/**
|
||||
* The type of C function.
|
||||
*/
|
||||
interface CFunctionType
|
||||
|
||||
/**
|
||||
* The type of C function constructed from some Kotlin function, possibly using an adapter.
|
||||
* The (non-abstract) implementation classes are supposed to be object declarations.
|
||||
*/
|
||||
interface CAdaptedFunctionType<F : Function<*>> : CFunctionType {
|
||||
|
||||
/**
|
||||
* Returns a raw pointer to C function of this type, which calls given Kotlin *static* function.
|
||||
*
|
||||
* This inconvenient method should not be used directly; use [staticCFunction] instead.
|
||||
*
|
||||
* @param function must be *static*, i.e. an (unbound) reference to a Kotlin function or
|
||||
* a closure which doesn't capture any variable
|
||||
*/
|
||||
fun fromStatic(function: F): NativePtr
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to `T`-typed C function which calls given Kotlin *static* function.
|
||||
* @see CAdaptedFunctionType.fromStatic
|
||||
*/
|
||||
inline fun <reified F : Function<*>, reified T : CAdaptedFunctionType<F>> staticCFunction(body: F): CFunctionPointer<T> {
|
||||
val type = CAdaptedFunctionType.getInstanceOf<T>()
|
||||
return interpretPointed<CFunction<T>>(type.fromStatic(body)).ptr
|
||||
}
|
||||
|
||||
/**
|
||||
* The C function.
|
||||
*/
|
||||
class CFunction<T : CFunctionType>(override val rawPtr: NativePtr) : CPointed
|
||||
|
||||
/**
|
||||
* The pointer to [CFunction].
|
||||
*/
|
||||
typealias CFunctionPointer<T> = CPointer<CFunction<T>>
|
||||
|
||||
/**
|
||||
* The variable containing a [CFunctionPointer].
|
||||
*/
|
||||
typealias CFunctionPointerVar<T> = CPointerVarWithValueMappedTo<CFunctionPointer<T>>
|
||||
@@ -0,0 +1,222 @@
|
||||
package kotlin_.cinterop
|
||||
|
||||
interface NativePlacement {
|
||||
|
||||
fun alloc(size: Long, align: Int): NativePointed
|
||||
|
||||
fun alloc(size: Int, align: Int) = alloc(size.toLong(), align)
|
||||
}
|
||||
|
||||
interface NativeFreeablePlacement : NativePlacement {
|
||||
fun free(mem: NativePointed)
|
||||
}
|
||||
|
||||
object nativeHeap : NativeFreeablePlacement {
|
||||
override fun alloc(size: Long, align: Int) = nativeMemUtils.alloc(size, align)
|
||||
|
||||
override fun free(mem: NativePointed) = nativeMemUtils.free(mem)
|
||||
}
|
||||
|
||||
// TODO: implement optimally
|
||||
class Arena(private val parent: NativeFreeablePlacement = nativeHeap) : NativePlacement {
|
||||
|
||||
private val allocatedChunks = mutableListOf<NativePointed>()
|
||||
|
||||
override fun alloc(size: Long, align: Int): NativePointed {
|
||||
val res = nativeHeap.alloc(size, align)
|
||||
try {
|
||||
allocatedChunks.add(res)
|
||||
return res
|
||||
} catch (e: Throwable) {
|
||||
nativeHeap.free(res)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
allocatedChunks.forEach {
|
||||
nativeHeap.free(it)
|
||||
}
|
||||
|
||||
allocatedChunks.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun NativePlacement.alloc(size: Int, align: Int) = alloc(size.toLong(), align)
|
||||
|
||||
/**
|
||||
* Allocates variable of given type.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline fun <reified T : CVariable> NativePlacement.alloc(): T =
|
||||
alloc(CVariable.sizeOf<T>(), CVariable.alignOf<T>()).reinterpret()
|
||||
|
||||
/**
|
||||
* Allocates C array of given elements type and length.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline fun <reified T : CVariable> NativePlacement.allocArray(length: Long): CArray<T> =
|
||||
alloc(CVariable.sizeOf<T>() * length, CVariable.alignOf<T>()).reinterpret()
|
||||
|
||||
/**
|
||||
* Allocates C array of given elements type and length.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline fun <reified T : CVariable> NativePlacement.allocArray(length: Int): CArray<T> =
|
||||
allocArray(length.toLong())
|
||||
|
||||
/**
|
||||
* Allocates C array of given elements type and length, and initializes its elements applying given block.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline fun <reified T : CVariable> NativePlacement.allocArray(length: Long,
|
||||
initializer: T.(Long)->Unit): CArray<T> {
|
||||
val res = allocArray<T>(length)
|
||||
|
||||
(0 until length).forEach {
|
||||
res[it].initializer(it)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates C array of given elements type and length, and initializes its elements applying given block.
|
||||
*
|
||||
* @param T must not be abstract
|
||||
*/
|
||||
inline fun <reified T : CVariable> NativePlacement.allocArray(length: Int, initializer: T.(Long)->Unit) =
|
||||
allocArray(length.toLong(), initializer)
|
||||
|
||||
|
||||
/**
|
||||
* Allocates C array of pointers to given elements.
|
||||
*/
|
||||
fun <T : CPointed> NativePlacement.allocArrayOfPointersTo(elements: List<T?>): CArray<CPointerVar<T>> {
|
||||
val res = allocArray<CPointerVar<T>>(elements.size)
|
||||
elements.forEachIndexed { index, value ->
|
||||
res[index].value = value?.ptr
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates C array of pointers to given elements.
|
||||
*/
|
||||
fun <T : CPointed> NativePlacement.allocArrayOfPointersTo(vararg elements: T?) =
|
||||
allocArrayOfPointersTo(elements.toList())
|
||||
|
||||
/**
|
||||
* Allocates C array of given values.
|
||||
*/
|
||||
inline fun <reified T : CPointer<*>>
|
||||
NativePlacement.allocArrayOf(vararg elements: T?): CArray<CPointerVarWithValueMappedTo<T>> {
|
||||
|
||||
return allocArrayOf(elements.toList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates C array of given values.
|
||||
*/
|
||||
inline fun <reified T : CPointer<*>>
|
||||
NativePlacement.allocArrayOf(elements: List<T?>): CArray<CPointerVarWithValueMappedTo<T>> {
|
||||
|
||||
val res = allocArray<CPointerVarWithValueMappedTo<T>>(elements.size)
|
||||
elements.forEachIndexed { index, value ->
|
||||
res[index].value = value
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
fun NativePlacement.allocArrayOf(elements: ByteArray): CArray<CInt8Var> {
|
||||
val res = allocArray<CInt8Var>(elements.size)
|
||||
elements.forEachIndexed { i, byte ->
|
||||
res[i].value = byte
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
fun <T : CPointed> NativePlacement.allocPointerTo() = alloc<CPointerVar<T>>()
|
||||
|
||||
/**
|
||||
* The zero-terminated string.
|
||||
*/
|
||||
class CString private constructor(override val rawPtr: NativePtr) : CPointed {
|
||||
|
||||
companion object {
|
||||
fun fromArray(array: CArray<CInt8Var>) = CString(array.rawPtr)
|
||||
}
|
||||
|
||||
fun length(): Int {
|
||||
val array = reinterpret<CArray<CInt8Var>>()
|
||||
|
||||
var res = 0
|
||||
while (array[res].value != 0.toByte()) {
|
||||
++res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val array = reinterpret<CArray<CInt8Var>>()
|
||||
|
||||
val bytes = ByteArray(this.length())
|
||||
bytes.forEachIndexed { i, byte ->
|
||||
bytes[i] = array[i].value
|
||||
}
|
||||
return String(bytes) // TODO: encoding
|
||||
}
|
||||
|
||||
fun asCharPtr() = reinterpret<CInt8Var>()
|
||||
}
|
||||
|
||||
fun CString.Companion.fromString(str: String?, placement: NativePlacement): CString? {
|
||||
if (str == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val bytes = str.toByteArray() // TODO: encoding
|
||||
val len = bytes.size
|
||||
val nativeBytes = nativeHeap.allocArray<CInt8Var>(len + 1)
|
||||
|
||||
bytes.forEachIndexed { i, byte ->
|
||||
nativeBytes[i].value = byte
|
||||
}
|
||||
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 private constructor(private val arena: Arena) : NativePlacement by arena {
|
||||
val memScope: NativePlacement
|
||||
get() = this
|
||||
|
||||
companion object {
|
||||
internal inline fun <R> use(block: MemScope.()->R): R {
|
||||
val memScope = MemScope(Arena())
|
||||
try {
|
||||
return memScope.block()
|
||||
} finally {
|
||||
memScope.arena.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs given [block] providing allocation of memory
|
||||
* which will be automatically disposed at the end of this scope.
|
||||
*/
|
||||
inline fun <R> memScoped(block: MemScope.()->R): R {
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") // TODO: it is a hack
|
||||
return MemScope.use(block)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* TODO: rename to kotlin
|
||||
*/
|
||||
package kotlin_;
|
||||
Reference in New Issue
Block a user