Use compiler-generated C stubs to call Objective-C methods
Also * Use compiler-generated C stubs when overriding all Objective-C methods * Support block types in C stubs generator
This commit is contained in:
committed by
SvyatoslavScherbina
parent
68efc823f1
commit
54881b421c
@@ -42,7 +42,7 @@ fun optional(): Nothing = throw RuntimeException("Do not call me!!!")
|
||||
|
||||
@Deprecated(
|
||||
"Add @OverrideInit to constructor to make it override Objective-C initializer",
|
||||
level = DeprecationLevel.WARNING
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_INIT_BY)
|
||||
external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T
|
||||
@@ -56,8 +56,7 @@ private fun ObjCObjectBase.superInitCheck(superInitCallResult: ObjCObject?) {
|
||||
throw UnsupportedOperationException("Super initializer has replaced object")
|
||||
}
|
||||
|
||||
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING)
|
||||
fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) // TODO: make private
|
||||
internal fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T)
|
||||
|
||||
@SymbolName("Kotlin_Interop_refFromObjC")
|
||||
external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T?
|
||||
@@ -93,8 +92,9 @@ var <T : Any?> ObjCNotImplementedVar<T>.value: T
|
||||
typealias ObjCStringVarOf<T> = ObjCNotImplementedVar<T>
|
||||
typealias ObjCBlockVar<T> = ObjCNotImplementedVar<T>
|
||||
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_GET_RECEIVER_OR_SUPER)
|
||||
external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer?
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_CREATE_SUPER_STRUCT)
|
||||
@PublishedApi
|
||||
internal external fun createObjCSuperStruct(receiver: NativePtr, superClass: NativePtr): NativePtr
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@@ -102,11 +102,7 @@ annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryNa
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCMethod(val selector: String, val bridge: String)
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCBridge(val selector: String, val encoding: String, val imp: String = "")
|
||||
annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false)
|
||||
|
||||
@Target(AnnotationTarget.CONSTRUCTOR)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@@ -114,7 +110,7 @@ annotation class ObjCConstructor(val initSelector: String, val designated: Boole
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCFactory(val bridge: String)
|
||||
annotation class ObjCFactory(val selector: String, val encoding: String, val isStret: Boolean = false)
|
||||
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@@ -158,10 +154,13 @@ private fun allocObjCObject(clazz: NativePtr): NativePtr {
|
||||
@kotlin.native.internal.ExportForCompiler
|
||||
private external fun <T : ObjCObject> getObjCClass(): NativePtr
|
||||
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER)
|
||||
external fun getMessenger(superClass: NativePtr): COpaquePointer?
|
||||
internal external fun getMessenger(superClass: NativePtr): COpaquePointer?
|
||||
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER_STRET)
|
||||
external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
|
||||
internal external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
|
||||
|
||||
|
||||
internal class ObjCWeakReferenceImpl : kotlin.native.ref.WeakReferenceImpl() {
|
||||
@@ -180,14 +179,18 @@ private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr)
|
||||
|
||||
// Konan runtme:
|
||||
|
||||
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.WARNING)
|
||||
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.ERROR)
|
||||
@SymbolName("Kotlin_Interop_CreateNSStringFromKString")
|
||||
external fun CreateNSStringFromKString(str: String?): NativePtr
|
||||
|
||||
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.WARNING)
|
||||
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.ERROR)
|
||||
@SymbolName("Kotlin_Interop_CreateKStringFromNSString")
|
||||
external fun CreateKStringFromNSString(ptr: NativePtr): String?
|
||||
|
||||
@PublishedApi
|
||||
@SymbolName("Kotlin_Interop_CreateObjCObjectHolder")
|
||||
internal external fun createObjCObjectHolder(ptr: NativePtr): Any?
|
||||
|
||||
// Objective-C runtime:
|
||||
|
||||
@SymbolName("objc_retainAutoreleaseReturnValue")
|
||||
|
||||
@@ -25,7 +25,7 @@ inline fun <R> autoreleasepool(block: () -> R): R {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING)
|
||||
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.ERROR)
|
||||
fun <T : ObjCObject> ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast<T>()
|
||||
|
||||
// TODO: null checks
|
||||
|
||||
+24
-197
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.native.interop.gen
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator
|
||||
import org.jetbrains.kotlin.native.interop.indexer.*
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List<String> {
|
||||
val selectorParts = this.selector.split(":")
|
||||
@@ -74,12 +73,11 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
override fun generate(context: StubGenerationContext): Sequence<String> =
|
||||
if (context.nativeBridges.isSupported(this)) {
|
||||
val result = mutableListOf<String>()
|
||||
result.add(objCMethodAnnotation)
|
||||
if (method.nsConsumesSelf) result.add("@CCall.ConsumesReceiver")
|
||||
if (method.nsReturnsRetained) result.add("@CCall.ReturnsRetained")
|
||||
result.addAll(objCMethodAnnotations)
|
||||
result.add(header)
|
||||
|
||||
if (method.isInit) {
|
||||
// TODO: generate only constructor/factory in this case.
|
||||
val kotlinScope = stubGenerator.kotlinFile
|
||||
|
||||
val newParameterNames = method.getKotlinParameterNames(forConstructorOrFactory = true)
|
||||
@@ -132,26 +130,13 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
|
||||
result.add("")
|
||||
result.add("@ObjCFactory".applyToStrings(bridgeName))
|
||||
result.addAll(buildObjCMethodAnnotations("@ObjCFactory"))
|
||||
result.add("external fun <T : $className> $receiver.create($parameters): $returnType")
|
||||
}
|
||||
is ObjCProtocol -> {} // Nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
val objCBridge = "@ObjCBridge".applyToStrings(
|
||||
*mutableListOf<String>().apply {
|
||||
add(method.selector)
|
||||
add(method.encoding)
|
||||
addIfNotNull(implementationTemplate)
|
||||
}.toTypedArray()
|
||||
)
|
||||
|
||||
context.addTopLevelDeclaration(
|
||||
listOf("@kotlin.native.internal.ExportForCompiler", objCBridge)
|
||||
+ block(bridgeHeader, bodyLines)
|
||||
)
|
||||
|
||||
result.asSequence()
|
||||
} else {
|
||||
sequenceOf(
|
||||
@@ -160,48 +145,18 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
)
|
||||
}
|
||||
|
||||
private val bodyLines: List<String>
|
||||
private val kotlinParameters: List<KotlinParameter>
|
||||
private val kotlinReturnType: String
|
||||
private val header: String
|
||||
private val implementationTemplate: String?
|
||||
internal val bridgeName: String
|
||||
private val bridgeHeader: String
|
||||
internal val objCMethodAnnotation: String
|
||||
internal val objCMethodAnnotations: List<String>
|
||||
private val isStret: Boolean
|
||||
|
||||
init {
|
||||
val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile)
|
||||
|
||||
kotlinParameters = mutableListOf()
|
||||
val kotlinObjCBridgeParameters = mutableListOf<KotlinParameter>()
|
||||
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
|
||||
|
||||
val kniReceiverParameter = "kniR"
|
||||
val kniSuperClassParameter = "kniSC"
|
||||
val kniSelectorParameter = "kniSEL"
|
||||
|
||||
val voidPtr = PointerType(VoidType)
|
||||
|
||||
val returnType = method.getReturnType(container.classOrProtocol)
|
||||
|
||||
val messengerGetter =
|
||||
if (returnType.isStret(stubGenerator.configuration.target)) "getMessengerStret" else "getMessenger"
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(kniSuperClassParameter, KotlinTypes.nativePtr))
|
||||
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)"))
|
||||
|
||||
if (method.nsConsumesSelf) {
|
||||
// TODO: do this later due to possible exceptions
|
||||
bodyGenerator.out("objc_retain($kniReceiverParameter)")
|
||||
}
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(kniReceiverParameter, KotlinTypes.nativePtr))
|
||||
nativeBridgeArguments.add(
|
||||
TypedKotlinValue(voidPtr,
|
||||
"getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)"))
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(kniSelectorParameter, KotlinTypes.cOpaquePointer))
|
||||
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, kniSelectorParameter))
|
||||
isStret = returnType.isStret(stubGenerator.configuration.target)
|
||||
|
||||
val kotlinParameterNames = method.getKotlinParameterNames()
|
||||
|
||||
@@ -211,9 +166,6 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
val kotlinType = stubGenerator.mirror(it.type).argType
|
||||
val annotatedName = if (it.nsConsumed) "@CCall.Consumed $name" else name
|
||||
kotlinParameters.add(KotlinParameter(annotatedName, kotlinType))
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(name, kotlinType))
|
||||
nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName()))
|
||||
}
|
||||
|
||||
this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
|
||||
@@ -222,55 +174,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
stubGenerator.mirror(returnType).argType
|
||||
}.render(stubGenerator.kotlinFile)
|
||||
|
||||
val result = stubGenerator.mappingBridgeGenerator.kotlinToNative(
|
||||
bodyGenerator,
|
||||
this@ObjCMethodStub,
|
||||
returnType,
|
||||
nativeBridgeArguments,
|
||||
independent = when (container) {
|
||||
is ObjCClassOrProtocol -> true // Every proper instance has this method in its method table.
|
||||
is ObjCCategory -> false // Method is contributed by native dependency.
|
||||
}
|
||||
) { nativeValues ->
|
||||
val messengerParameterTypes = mutableListOf<String>()
|
||||
messengerParameterTypes.add("void*")
|
||||
messengerParameterTypes.add("SEL")
|
||||
method.parameters.forEach {
|
||||
messengerParameterTypes.add(it.getTypeStringRepresentation())
|
||||
}
|
||||
|
||||
val messengerReturnType = returnType.getStringRepresentation()
|
||||
|
||||
val messengerType =
|
||||
"$messengerReturnType (* ${method.cAttributes}) (${messengerParameterTypes.joinToString()})"
|
||||
|
||||
val messenger = "(($messengerType) ${nativeValues.first()})"
|
||||
|
||||
val messengerArguments = nativeValues.drop(1)
|
||||
|
||||
"$messenger(${messengerArguments.joinToString()})"
|
||||
}
|
||||
bodyGenerator.returnResult(result)
|
||||
|
||||
this.implementationTemplate = if (needsImplementationTemplate()) {
|
||||
genImplementationTemplate(stubGenerator)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
this.bodyLines = bodyGenerator.build()
|
||||
|
||||
bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}"
|
||||
objCMethodAnnotation = "@ObjCMethod".applyToStrings(method.selector, bridgeName)
|
||||
|
||||
this.bridgeHeader = buildString {
|
||||
append("internal fun ")
|
||||
append(bridgeName)
|
||||
append('(')
|
||||
kotlinObjCBridgeParameters.renderParametersTo(stubGenerator.kotlinFile, this)
|
||||
append("): ")
|
||||
append(kotlinReturnType)
|
||||
}
|
||||
objCMethodAnnotations = buildObjCMethodAnnotations("@ObjCMethod")
|
||||
|
||||
val joinedKotlinParameters = kotlinParameters.renderParameters(stubGenerator.kotlinFile)
|
||||
|
||||
@@ -302,24 +206,26 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
private fun needsImplementationTemplate(): Boolean =
|
||||
method.getReturnType(container.classOrProtocol).isBlockPointer() ||
|
||||
method.parameters.any { it.type.isBlockPointer() }
|
||||
private fun buildObjCMethodAnnotations(main: String) = listOfNotNull(
|
||||
buildObjCMethodAnnotation(main),
|
||||
"@CCall.ConsumesReceiver".takeIf { method.nsConsumesSelf },
|
||||
"@CCall.ReturnsRetained".takeIf { method.nsReturnsRetained }
|
||||
)
|
||||
|
||||
private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) {
|
||||
is ObjCClassOrProtocol -> {
|
||||
val codeBuilder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope)
|
||||
|
||||
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
|
||||
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
|
||||
result
|
||||
private fun buildObjCMethodAnnotation(annotation: String) = buildString {
|
||||
append(annotation)
|
||||
append('(')
|
||||
append(method.selector.quoteAsKotlinLiteral())
|
||||
append(", ")
|
||||
append(method.encoding.quoteAsKotlinLiteral())
|
||||
if (isStret) {
|
||||
append(", ")
|
||||
append("true")
|
||||
}
|
||||
is ObjCCategory -> ""
|
||||
append(')')
|
||||
}
|
||||
}
|
||||
|
||||
private fun Type.isBlockPointer() = this.unwrapTypedefs() is ObjCBlockPointer
|
||||
|
||||
private fun deprecatedInit(className: String, initParameterNames: List<String>, factory: Boolean): String {
|
||||
val replacement = if (factory) "$className.create" else className
|
||||
val replacementKind = if (factory) "factory method" else "constructor"
|
||||
@@ -720,11 +626,11 @@ class ObjCPropertyStub(
|
||||
}
|
||||
val result = mutableListOf(
|
||||
"$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType",
|
||||
" ${getterStub.objCMethodAnnotation} external get"
|
||||
" ${getterStub.objCMethodAnnotations.joinToString(" ")} external get"
|
||||
)
|
||||
|
||||
property.setter?.let {
|
||||
result.add(" ${setterStub!!.objCMethodAnnotation} external set")
|
||||
result.add(" ${setterStub!!.objCMethodAnnotations.joinToString(" ")} external set")
|
||||
}
|
||||
|
||||
return result.asSequence()
|
||||
@@ -741,85 +647,6 @@ fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String {
|
||||
return if (isMeta) "${baseClassName}Meta" else baseClassName
|
||||
}
|
||||
|
||||
private fun Parameter.getTypeStringRepresentation() =
|
||||
(if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation()
|
||||
|
||||
private fun ObjCMethod.getSelfTypeStringRepresentation() = if (this.nsConsumesSelf) {
|
||||
"__attribute__((ns_consumed)) id"
|
||||
} else {
|
||||
"id"
|
||||
}
|
||||
|
||||
val ObjCMethod.cAttributes get() = if (this.nsReturnsRetained) {
|
||||
"__attribute__((ns_returns_retained)) "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
private fun NativeCodeBuilder.genMethodImp(
|
||||
stubGenerator: StubGenerator,
|
||||
nativeBacked: NativeBacked,
|
||||
method: ObjCMethod,
|
||||
container: ObjCClassOrProtocol
|
||||
): String {
|
||||
|
||||
val returnType = method.getReturnType(container)
|
||||
val cReturnType = returnType.getStringRepresentation()
|
||||
|
||||
val bridgeArguments = mutableListOf<TypedNativeValue>()
|
||||
|
||||
val parameters = mutableListOf<Pair<String, String>>()
|
||||
|
||||
parameters.add("self" to method.getSelfTypeStringRepresentation())
|
||||
|
||||
val receiverType = ObjCIdType(ObjCPointer.Nullability.NonNull, protocols = emptyList())
|
||||
bridgeArguments.add(TypedNativeValue(receiverType, "self"))
|
||||
|
||||
parameters.add("_cmd" to "SEL")
|
||||
|
||||
method.parameters.forEachIndexed { index, parameter ->
|
||||
val name = "p$index"
|
||||
parameters.add(name to parameter.getTypeStringRepresentation())
|
||||
bridgeArguments.add(TypedNativeValue(parameter.type, name))
|
||||
}
|
||||
|
||||
val functionName = "knimi_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId()
|
||||
val functionAttr = method.cAttributes
|
||||
|
||||
out("$cReturnType $functionName(${parameters.joinToString { it.second + " " + it.first }}) $functionAttr{")
|
||||
|
||||
val callExpr = stubGenerator.mappingBridgeGenerator.nativeToKotlin(
|
||||
this,
|
||||
nativeBacked,
|
||||
returnType,
|
||||
bridgeArguments
|
||||
) { kotlinValues ->
|
||||
|
||||
val kotlinReceiverType = stubGenerator.declarationMapper
|
||||
.getKotlinClassFor(container, isMeta = method.isClass)
|
||||
.type.render(stubGenerator.kotlinFile)
|
||||
|
||||
val kotlinRawReceiver = kotlinValues.first()
|
||||
val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<$kotlinReceiverType>()"
|
||||
|
||||
val namedArguments = kotlinValues.drop(1).zip(method.getKotlinParameterNames()) { value, name ->
|
||||
"${name.asSimpleName()} = $value"
|
||||
}
|
||||
|
||||
"${kotlinReceiver}.${method.kotlinName.asSimpleName()}(${namedArguments.joinToString()})"
|
||||
}
|
||||
|
||||
if (returnType.unwrapTypedefs() is VoidType) {
|
||||
out(" $callExpr;")
|
||||
} else {
|
||||
out(" return $callExpr;")
|
||||
}
|
||||
|
||||
out("}")
|
||||
|
||||
return functionName
|
||||
}
|
||||
|
||||
private fun genProtocolGetter(
|
||||
stubGenerator: StubGenerator,
|
||||
nativeBacked: NativeBacked,
|
||||
|
||||
Reference in New Issue
Block a user