[Kotlin/Native][Interop] Provide pure c wrappers over cpp for skia interop

This commit is contained in:
Vladimir Ivanov
2019-08-19 11:44:20 +03:00
committed by Alexander Gorshenev
parent 61825e9aec
commit 5f582ad28a
31 changed files with 1525 additions and 66 deletions
+43
View File
@@ -0,0 +1,43 @@
Current status
==============
Limited C++ interop provided via plain C wrappers and cinterop mechanism.
Usage:
------
Add `Language = C++` to .def file or `compilerOpts = -x c++` to command line or def file.
C++ features
------------
* C++ class:
+ Virtual and non-virtual methods
* Static methods mapped as companion ones
* Fields
* Static fields as companion ones
* Constructors are mapped to `__init__(args)` companion methods
* Destructor is mapped to `__destroy__(this)` companion method
* LValueReference parameters and return value internally handled as pointers
* Namespaces provided as simple mangled class name. It works but awfully ugly. Shall be fixed by mapping to packages.
* Nested C++ classes: same as namespaces, simple mangling. Shall be fixed.
* Access modifiers: only public members exposed to Kotlin. Anonymous namespaces are silently ignored.
Known issues
------------
* C++ object with nontrivial copy constructor may work incorrect when used as by value parameter or return typr.
"Nontrivial" in this context relates to objects which bahavior depends on memory location. Most of other non-POD objects may be handled well.
In fact, "by value" return type is mapped to CValue which is immutrable movable block. Particularly, non-const methods invoked with CValue receiver
via useContents mechanism run on temporary object and therefore does not modify the original copy. TBD.
Limitations
-----------
* Operators are not supported yet (silently ignored)
* LVReference is mapped to CPointer<T>? which is incorrect (should be notNull). This may cause segmentation fault in case of null would be sent as a parameter. TBD
* const overload not supported and cause compilation error. That is, two class methods with the same signature (`const` and `non-const`) can't be compiled. The same for function parameters: if two functions differ only in `const*` modifier of parameter, this will cause "conflicting overloads" error. TBD.
* C++ lambda type is not supported yet.
* Member pointer, member reference, rvalue reference and some other types are not wupported.
* Inheritance is not implemented yet. C++-style callbacks (overriding virtual method in Kotlin) may be implemented via plain C bridge (this can be done by hand as a workaround). TBD.
@@ -32,6 +32,8 @@ private class StructDefImpl(
size, align, decl
) {
override val members = mutableListOf<StructMember>()
override val methods = mutableListOf<FunctionDecl>()
override val staticFields = mutableListOf<GlobalDecl>()
}
private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) {
@@ -74,6 +76,30 @@ private class ObjCCategoryImpl(
override val properties = mutableListOf<ObjCProperty>()
}
private fun getParentName(cursor: CValue<CXCursor>, pkg: List<String> = emptyList()) : String? { // }: List<String>? {
// This doesn't work for anonymous C++ struct (such as typedef struct { void foo(); } TypeDefName) as well as anon namespace
// In contrast, clang_getTypeSpelling return fully qualified name for struct & class (incl. typedef anon struct),
// but does not help for anything elde such as template member, namespace etc
// So, TODO Use ultimately clang_getTypeSpelling for CXType_Record (no traversing needed) and traverse up the whole hierarchy for anythiong else
// Unfortunately, this won't work too for variable decl with anon type like that: ''struct { void foo(); } x;''
// while function is accessible as x.foo()
// skip this (zero) level:
val parent = clang_getCursorSemanticParent(cursor)
if (clang_isDeclaration(parent.kind) == 0)
return if (pkg.isNotEmpty()) pkg.joinToString("::") else null
val type = clang_getCursorType(parent)
if (type.kind == CXTypeKind.CXType_Record)
return clang_getTypeSpelling(type).convertAndDispose()
val nextPkg = if (parent.kind == CXCursorKind.CXCursor_Namespace) listOf(parent.spelling) + pkg else pkg
return getParentName(parent, nextPkg)
}
internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean = false) : NativeIndex() {
private sealed class DeclarationID {
@@ -136,10 +162,10 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
override val typedefs get() = typedefRegistry.included
private val typedefRegistry = TypeDeclarationRegistry<TypedefDef>()
private val functionById = mutableMapOf<DeclarationID, FunctionDecl>()
private val functionById = mutableMapOf<DeclarationID, FunctionDecl?>()
override val functions: Collection<FunctionDecl>
get() = functionById.values
get() = functionById.values.filterNotNull()
override val macroConstants = mutableListOf<ConstantDef>()
override val wrappedMacros = mutableListOf<WrappedMacroDef>()
@@ -191,6 +217,43 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return StructDeclImpl(typeSpelling, getLocation(cursor))
}
private fun visitClass(cursor: CValue<CXCursor>, clazz: StructDefImpl) {
// TODO skip method (function) when encounter UnsupportedType in params or ret value. Otherwise all class methods will be lost due to exception (?)
visitChildren(cursor) { cursor, _ ->
if (cursor.isPublic) {
// TODO If a kotlin class is _conceptually_ derived from its c++ counterpart, then it shall be able to override virtual private and access protected
when (cursor.kind) {
CXCursorKind.CXCursor_CXXMethod -> {
val isOperatorFunction = (clang_getCursorSpelling(cursor).convertAndDispose().take(8) == "operator")
// operators are Not Implemented Yet
if (!isOperatorFunction) {
if (clang_isFunctionTypeVariadic(clang_getCursorType(cursor)) == 0) // FIXME why it doesn't work???
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
}
}
CXCursorKind.CXCursor_Constructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
CXCursorKind.CXCursor_Destructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
CXCursorKind.CXCursor_VarDecl -> {
clazz.staticFields.add(GlobalDecl(
name =getCursorSpelling(cursor),
type = convertCursorType(cursor),
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0,
parentName = clazz.decl.spelling)
)
}
else -> {
}
}
}
CXChildVisitResult.CXChildVisit_Continue
}
}
private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue<CXCursor>) {
val type = clang_getCursorType(cursor)
@@ -205,17 +268,19 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
when (cursor.kind) {
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
else -> error(cursor.kind)
}
)
structDef.members += fields
visitClass(cursor, structDef)
structDecl.def = structDef
}
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) {
getFields(containerType).forEach { fieldCursor ->
getFields(containerType).filter { it.isPublic }.forEach { fieldCursor ->
val name = getCursorSpelling(fieldCursor)
if (name.isNotEmpty()) {
val fieldType = convertCursorType(fieldCursor)
@@ -465,7 +530,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor))
private inline fun objCType(supplier: () -> ObjCPointer) = when (library.language) {
Language.C -> UnsupportedType
Language.C, Language.CPP -> UnsupportedType
Language.OBJECTIVE_C -> supplier()
}
@@ -582,7 +647,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
CXType_Record -> RecordType(getStructDeclAt(clang_getTypeDeclaration(type)))
CXType_Enum -> EnumType(getEnumDefAt(clang_getTypeDeclaration(type)))
CXType_Pointer -> {
CXType_Pointer, CXType_LValueReference -> {
val pointeeType = clang_getPointeeType(type)
val pointeeIsConst =
(clang_isConstQualifiedType(clang_getCanonicalType(pointeeType)) != 0)
@@ -590,7 +655,9 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
val convertedPointeeType = convertType(pointeeType)
PointerType(
if (convertedPointeeType == UnsupportedType) VoidType else convertedPointeeType,
pointeeIsConst = pointeeIsConst
pointeeIsConst = pointeeIsConst,
isLVReference = (kind == CXType_LValueReference),
spelling = type.name
)
}
@@ -794,23 +861,43 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return
}
val namespace: Namespace? =
// semantic parent of any namespace member is always namespace itself (no type aliases etc)
if (info.semanticContainer!!.pointed.cursor.kind == CXCursorKind.CXCursor_Namespace) {
val parent = info.semanticContainer!!.pointed.cursor.readValue()
Namespace(getCursorSpelling(parent), getParentName(parent))
} else null
if (!cursor.isRecursivelyPublic()) {
// c++ : skip anon namespaces, static functions and variables and private inner classes
return
}
/**
* TODO It may be better to look at CXTypeKind instead of CXIdxEntity to distinguish C++ classes from templates
* C++ templates are also CXIdxEntity_CXXClass but CXCursor_ClassTemplate,
* while C++ class is CXCursor_ClassDecl
* The same for CXCursor_FunctionDecl vs CXCursor_FunctionTemplate
*/
when (kind) {
CXIdxEntity_Struct, CXIdxEntity_Union -> {
CXIdxEntity_Struct, CXIdxEntity_Union, CXIdxEntity_CXXClass -> {
if (entityName == null) {
// Skip anonymous struct.
// (It gets included anyway if used as a named field type).
} else {
getStructDeclAt(cursor)
if (library.language != Language.CPP) {
getStructDeclAt(cursor)
}
}
}
CXIdxEntity_Typedef -> {
CXIdxEntity_Typedef, CXIdxEntity_CXXTypeAlias -> {
val type = clang_getCursorType(cursor)
getTypedef(type)
}
CXIdxEntity_Function -> {
if (isSuitableFunction(cursor)) {
if (isSuitableFunction(cursor)
&& library.language != Language.CPP) {
functionById.getOrPut(getDeclarationId(cursor)) {
getFunction(cursor)
}
@@ -822,13 +909,15 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
CXIdxEntity_Variable -> {
if (info.semanticContainer!!.pointed.cursor.kind == CXCursorKind.CXCursor_TranslationUnit) {
// Top-level variable.
val parentKind = info.semanticContainer!!.pointed.cursor.kind
if (parentKind == CXCursorKind.CXCursor_TranslationUnit || parentKind == CXCursorKind.CXCursor_Namespace) {
// Top-level or namespace member. Skip class static members - they are loaded by visitClass
globalById.getOrPut(getDeclarationId(cursor)) {
GlobalDecl(
name = entityName!!,
type = convertCursorType(cursor),
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0,
parentName = getParentName(cursor)
)
}
}
@@ -880,6 +969,49 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
}
fun indexDeclaration(cursor: CValue<CXCursor>): Unit {
if (!library.includesDeclaration(cursor)) {
return
}
if (cursor.isRecursivelyPublic()) {
when (cursor.kind) {
CXCursorKind.CXCursor_ClassDecl, CXCursorKind.CXCursor_StructDecl, CXCursorKind.CXCursor_UnionDecl -> {
if (library.language == Language.CPP) {
if (cursor.spelling.isEmpty()) {
// Skip anonymous struct.
// (It gets included anyway if used as a named field type).
} else {
getStructDeclAt(cursor)
}
}
}
CXCursorKind.CXCursor_FunctionDecl -> {
if (library.language == Language.CPP) {
indexCxxFunction(cursor)
}
}
else -> {
}
}
}
}
private fun indexCxxFunction(cursor: CValue<CXCursor>) {
if (isSuitableFunction(cursor)) {
if (getCursorSpelling(cursor).take(8) == "operator") {
// not implemented yet
} else {
functionById.getOrPut(getDeclarationId(cursor)) {
getFunction(cursor)
}
}
}
}
fun indexObjCClass(cursor: CValue<CXCursor>) {
if (isAvailable(cursor)) {
getObjCClassAt(cursor)
@@ -892,14 +1024,19 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
}
private fun getFunction(cursor: CValue<CXCursor>): FunctionDecl {
val name = clang_getCursorSpelling(cursor).convertAndDispose()
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
private fun getFunction(cursor: CValue<CXCursor>, receiver: StructDecl? = null): FunctionDecl? {
if (!isFuncDeclEligible(cursor)) {
log("Skip function ${clang_getCursorSpelling(cursor).convertAndDispose()}")
return null
}
var name = clang_getCursorSpelling(cursor).convertAndDispose()
var returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
val parameters = getFunctionParameters(cursor)
val parameters = mutableListOf<Parameter>()
parameters += getFunctionParameters(cursor)
val binaryName = when (library.language) {
Language.C, Language.OBJECTIVE_C -> clang_Cursor_getMangling(cursor).convertAndDispose()
Language.C, Language.CPP, Language.OBJECTIVE_C -> clang_Cursor_getMangling(cursor).convertAndDispose()
}
val definitionCursor = clang_getCursorDefinition(cursor)
@@ -907,7 +1044,40 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
val isVararg = clang_Cursor_isVariadic(cursor) != 0
return FunctionDecl(name, parameters, returnType, binaryName, isDefined, isVararg)
// TODO Do the following if clang_getCursorLanguage(cursor) == CXLanguageKind.CXLanguage_CPlusPlus ...
val parentName = getParentName(cursor)
val cxxMethodInfo = receiver?.let { CxxMethodInfo(
PointerType(RecordType(receiver),
clang_CXXMethod_isConst(cursor) != 0), // CXCursor_ConversionFunction has constness too
when (cursor.kind) {
CXCursorKind.CXCursor_Constructor -> {
returnType = PointerType(RecordType(receiver))
name = "__init__" // It is intended to init preallocated memory with placement new, so it is not "create" factory method. TODO One may want "create" method also.
// Parameter type for placement new is void*, but I want to emphasize that memory block ahall have proper size and alignment
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false))
CxxMethodKind.Constructor
}
CXCursorKind.CXCursor_Destructor -> {
name = "__destroy__"
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false))
CxxMethodKind.Destructor
}
// CXCursorKind.CXCursor_ConversionFunction -> ...
CXCursorKind.CXCursor_CXXMethod ->
if (clang_CXXMethod_isStatic(cursor) != 0) {
CxxMethodKind.StaticMethod
} else {
parameters.add(0, Parameter("self",
PointerType(RecordType(receiver), clang_CXXMethod_isConst(cursor) != 0),
false))
CxxMethodKind.InstanceMethod
}
else -> CxxMethodKind.None // Not implemented. Not expected, OK to assert (?)
}
)
}
return FunctionDecl(name, parameters, returnType, binaryName, isDefined, isVararg, parentName, cxxMethodInfo)
}
private fun getObjCMethod(cursor: CValue<CXCursor>): ObjCMethod? {
@@ -957,6 +1127,22 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
CXAvailabilityKind.CXAvailability_NotAccessible -> false
}
// Skip functions which parameter or return type is TemplateRef
private fun isFuncDeclEligible(cursor: CValue<CXCursor>): Boolean {
var ret = true
visitChildren(cursor) { cursor, _ ->
when (cursor.kind) {
CXCursorKind.CXCursor_TemplateRef -> {
ret = false
CXChildVisitResult.CXChildVisit_Break
}
else -> CXChildVisitResult.CXChildVisit_Recurse
}
}
return ret
}
private fun getFunctionParameters(cursor: CValue<CXCursor>): List<Parameter> {
val argNum = clang_Cursor_getNumArguments(cursor)
val args = (0..argNum - 1).map {
@@ -1024,6 +1210,13 @@ private fun indexDeclarations(nativeIndex: NativeIndexImpl): CompilationWithPCH
}
})
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
if (getContainingFile(cursor) in headers) {
nativeIndex.indexDeclaration(cursor)
}
CXChildVisitResult.CXChildVisit_Recurse
}
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
val file = getContainingFile(cursor)
if (file in headers && nativeIndex.library.includesDeclaration(cursor)) {
@@ -171,7 +171,7 @@ private fun reparseWithCodeSnippets(library: CompilationWithPCH,
names.forEach { name ->
val codeSnippetLines = when (library.language) {
Language.C, Language.OBJECTIVE_C ->
Language.C, Language.CPP, Language.OBJECTIVE_C ->
listOf("void $CODE_SNIPPET_FUNCTION_NAME_PREFIX$name() {",
" __auto_type KNI_INDEXER_VARIABLE_$name = $name;",
"}")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.native.interop.indexer
enum class Language(val sourceFileExtension: String) {
C("c"),
CPP("cpp"),
OBJECTIVE_C("m")
}
@@ -156,10 +157,12 @@ abstract class StructDecl(val spelling: String) : TypeDeclaration {
abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) {
enum class Kind {
STRUCT, UNION
STRUCT, UNION, CLASS
}
abstract val methods: List<FunctionDecl>
abstract val members: List<StructMember>
abstract val staticFields: List<GlobalDecl>
abstract val kind: Kind
val fields: List<Field> get() = members.filterIsInstance<Field>()
@@ -224,11 +227,53 @@ abstract class ObjCCategory(val name: String, val clazz: ObjCClass) : ObjCContai
*/
data class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
enum class CxxMethodKind {
None, // not supported yet?
Constructor,
Destructor,
StaticMethod,
InstanceMethod // virtual or non-virtual instance member method (non-static)
// do we need operators here?
// do we need to distinguish virtual and non-virtual? Static? Final?
}
/**
* C++ class method, constructor or destructor details
*/
class CxxMethodInfo(val receiverType: PointerType, val kind: CxxMethodKind = CxxMethodKind.InstanceMethod)
fun CxxMethodInfo.isConst() : Boolean = receiverType.pointeeIsConst
/**
* C function declaration.
*/
class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type, val binaryName: String,
val isDefined: Boolean, val isVararg: Boolean)
val isDefined: Boolean, val isVararg: Boolean,
val parentName: String? = null, val cxxMethod: CxxMethodInfo? = null) {
val fullName: String = parentName?.let { "$parentName::$name" } ?: name
// C++ virtual or non-virtual instance member, i.e. has "this" receiver
val isCxxInstanceMethod: Boolean = cxxMethod != null && cxxMethod.kind == CxxMethodKind.InstanceMethod
/**
* C++ class or instance member function, i.e. any function in the scope of class/struct: method, static, ctor, dtor, cast operator, etc
*/
val isCxxMethod: Boolean = cxxMethod != null
&& this.cxxMethod.kind != CxxMethodKind.None
val isCxxConstructor: Boolean = cxxMethod != null && this.cxxMethod.kind == CxxMethodKind.Constructor
val isCxxDestructor: Boolean = cxxMethod != null && this.cxxMethod.kind == CxxMethodKind.Destructor
val cxxReceiverType: PointerType? = cxxMethod?.receiverType
val cxxReceiverClass: StructDecl? = cxxMethod?. let { (this.cxxMethod.receiverType.pointeeType as RecordType).decl }
}
class Namespace(val name: String, val parent: String? = null) {
val fullName: String = parent?.let { "$parent::$name" } ?: name
}
/**
* C typedef definition.
@@ -248,7 +293,9 @@ class StringConstantDef(name: String, type: Type, val value: String) : ConstantD
class WrappedMacroDef(name: String, val type: Type) : MacroDef(name)
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean)
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean, val parentName: String? = null) {
val fullName: String = parentName?.let { "$it::$name" } ?: name
}
/**
* C type.
@@ -279,7 +326,9 @@ data class RecordType(val decl: StructDecl) : Type
data class EnumType(val def: EnumDef) : Type
data class PointerType(val pointeeType: Type, val pointeeIsConst: Boolean = false) : Type
// when pointer type is provided by clang we'll use ots correct spelling
data class PointerType(val pointeeType: Type, val pointeeIsConst: Boolean = false,
val isLVReference: Boolean = false, val spelling: String? = null) : Type
// TODO: refactor type representation and support type modifiers more generally.
data class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type
@@ -36,6 +36,46 @@ internal val CValue<CXType>.name: String get() = clang_getTypeSpelling(this).con
internal val CXTypeKind.spelling: String get() = clang_getTypeKindSpelling(this).convertAndDispose()
internal val CXCursorKind.spelling: String get() = clang_getCursorKindSpelling(this).convertAndDispose()
internal val CValue<CXCursor>.isPublic: Boolean get() {
val access = clang_getCXXAccessSpecifier(this)
return access != CX_CXXAccessSpecifier.CX_CXXProtected && access != CX_CXXAccessSpecifier.CX_CXXPrivate
}
/**
* TODO Accessibility needs better support
* Currently we provide binding (access) to static vars (= internal linkage)
* (i.e. following C policy, as the C header would be included into kotlin impl file
* Consistent approach to C++ would be:
* - Kotlin class inherits from C++ allowing overriding and protected access
* - namespace mapped to package
* - anon namespace members mapped to "internal" allowing access from the current translation unit
* To make this working we have to derive a complete C++ "proxy" class for each original one and declare C wrappers as friends
* BTW Such derived C++ proxy class is the only way to allow Kotlin to override the private virtual C++ methods (which is OK in C++)
* Without that C++ style callbacks via overriding would be limited or not supported
*/
internal fun CValue<CXCursor>.isRecursivelyPublic(): Boolean {
when {
clang_isDeclaration(kind) == 0 ->
return true // got the topmost declaration already
!isPublic ->
return false
kind == CXCursorKind.CXCursor_Namespace && getCursorSpelling(this).isEmpty() ->
return false
/*
* TODO FIXME In the current design we allow binding to static vars, but this won't work for anon namespaces and private members
* Need better (consistent( decision wrt accessibility.
*/
// clang_getCursorLinkage(this) == CXLinkageKind.CXLinkage_Internal ->
// return false; // check disabled for a while
else ->
return clang_getCursorSemanticParent(this).isRecursivelyPublic()
}
}
internal fun CValue<CXString>.convertAndDispose(): String {
try {
return clang_getCString(this)!!.toKString()
@@ -41,7 +41,7 @@ typealias KotlinExpression = String
fun String.asSimpleName(): String = if (this in kotlinKeywords || this.contains("$")) {
"`$this`"
} else {
this
this.replace(':', '_')
}
/**
@@ -16,10 +16,7 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.RecordType
import org.jetbrains.kotlin.native.interop.indexer.Type
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
import org.jetbrains.kotlin.native.interop.indexer.*
/**
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
@@ -27,7 +24,8 @@ import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
*/
class MappingBridgeGeneratorImpl(
val declarationMapper: DeclarationMapper,
val simpleBridgeGenerator: SimpleBridgeGenerator
val simpleBridgeGenerator: SimpleBridgeGenerator,
val language: Language
) : MappingBridgeGenerator {
override fun kotlinToNative(
@@ -40,7 +38,12 @@ class MappingBridgeGeneratorImpl(
): KotlinExpression {
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
kotlinValues.forEach { (type, value) ->
if (nativeBacked is FunctionStub && nativeBacked.isCxxInstanceMember()) {
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "rawPtr"))
kotlinValues.drop(1)
} else {
kotlinValues
}.forEach { (type, value) ->
if (type.unwrapTypedefs() is RecordType) {
builder.pushMemScoped()
val bridgeArgument = "$value.getPointer(memScope).rawValue"
@@ -79,6 +82,25 @@ class MappingBridgeGeneratorImpl(
val unwrappedType = type.unwrapTypedefs()
if (unwrappedType is RecordType) {
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
} else if (language == Language.CPP) {
// C++ is more restrictive wrt type conversion
val cppRefTypePrefix = if (unwrappedType is PointerType && unwrappedType.isLVReference) "*" else ""
when { /// TODO Move this cludge to mirror()
type is Typedef ->
nativeValues.add("(${type.def.name})${bridgeNativeValues[index]}")
type is PointerType && type.spelling != null ->
nativeValues.add("(${type.spelling})$cppRefTypePrefix${bridgeNativeValues[index]}")
unwrappedType is EnumType ->
nativeValues.add("(${unwrappedType.def.spelling})${bridgeNativeValues[index]}")
unwrappedType is RecordType ->
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
else ->
nativeValues.add(cppRefTypePrefix +
mirror(declarationMapper, type).info.cFromBridged(
bridgeNativeValues[index], scope, nativeBacked
)
)
}
} else {
nativeValues.add(
mirror(declarationMapper, type).info.cFromBridged(
@@ -90,18 +112,27 @@ class MappingBridgeGeneratorImpl(
val nativeResult = block(nativeValues)
when (unwrappedReturnType) {
is VoidType -> {
when {
unwrappedReturnType is VoidType -> {
out(nativeResult + ";")
""
}
is RecordType -> {
unwrappedReturnType is RecordType -> {
val kniStructResult = "kniStructResult"
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
if (language == Language.CPP) {
// use copy/move constructor to create object in place.
out("new(${bridgeNativeValues.last()}) ${unwrappedReturnType.decl.spelling}($nativeResult);")
} else {
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
// The following would be better, but won't work in case of const fields: C99 6.3.2.1p1
// out("*(${unwrappedReturnType.decl.spelling}*) ${bridgeNativeValues.last()} = $nativeResult;")
}
""
}
unwrappedReturnType is PointerType && unwrappedReturnType.isLVReference ->
"&$nativeResult"
else -> {
nativeResult
}
@@ -204,4 +235,4 @@ class MappingBridgeGeneratorImpl(
return result
}
}
}
@@ -120,10 +120,11 @@ class SimpleBridgeGeneratorImpl(
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
}
KotlinPlatform.NATIVE -> {
val externCPrefix = if (libraryForCStubs.language == Language.CPP) "extern \"C\" " else ""
val functionName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
if (independent) kotlinLines.add("@" + topLevelKotlinScope.reference(KotlinTypes.independent))
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
"$cReturnType $functionName ($joinedCParameters)"
"$externCPrefix$cReturnType $functionName ($joinedCParameters)"
}
}
nativeLines.add(cFunctionHeader + " {")
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.native.interop.indexer.*
fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
StructDef.Kind.STRUCT -> tryRenderStruct(def)
StructDef.Kind.UNION -> tryRenderUnion(def)
StructDef.Kind.CLASS -> tryRenderStruct(def)
}
private fun tryRenderStruct(def: StructDef): String? {
@@ -63,7 +63,7 @@ class StubIrBridgeBuilder(
)
private val mappingBridgeGenerator: MappingBridgeGenerator =
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, context.libraryForCStubs.language)
private val propertyAccessorBridgeBodies = mutableMapOf<PropertyAccessor, String>()
private val functionBridgeBodies = mutableMapOf<FunctionStub, List<String>>()
@@ -260,6 +260,9 @@ class StubIrBridgeBuilder(
isVararg = isVararg or parameter.isVararg
val parameterName = parameter.name.asSimpleName()
val bridgeArgument = when {
function.isCxxInstanceMember() && index == 0 -> {
"rawPtr"
}
parameter in builderResult.bridgeGenerationComponents.cStringParameters -> {
bodyGenerator.pushMemScoped()
"$parameterName?.cstr?.getPointer(memScope)"
@@ -289,7 +292,18 @@ class StubIrBridgeBuilder(
bridgeArguments,
independent = false
) { nativeValues ->
"${origin.function.name}(${nativeValues.joinToString()})"
with (origin.function) {
when {
isCxxInstanceMethod ->
"(${nativeValues[0]})->${name}(${nativeValues.drop(1).joinToString()})"
isCxxConstructor ->
"new(${nativeValues[0]}) ${cxxReceiverClass!!.spelling}(${nativeValues.drop(1).joinToString()})"
isCxxDestructor ->
"(${nativeValues[0]})->~${cxxReceiverClass!!.spelling?.substringAfterLast(':')}()"
else ->
"${fullName}(${nativeValues.joinToString()})"
}
}
}
bodyGenerator.returnResult(result)
functionBridgeBodies[function] = bodyGenerator.build()
@@ -28,12 +28,15 @@ class StubIrContext(
if (platform == KotlinPlatform.JVM) {
add("jni.h")
}
if (configuration.library.language == Language.CPP) {
add("new")
}
addAll(configuration.library.includes)
},
compilerArgs = configuration.library.compilerArgs,
additionalPreambleLines = configuration.library.additionalPreambleLines +
when (configuration.library.language) {
Language.C -> emptyList()
Language.C, Language.CPP -> emptyList()
Language.OBJECTIVE_C -> listOf("void objc_terminate();")
}
).precompileHeaders()
@@ -80,6 +80,17 @@ internal class StructStubBuilder(
}
val classifier = context.getKotlinClassForPointed(decl)
var methods: List<FunctionStub> =
def.methods
.filter { it.isCxxInstanceMethod }
.map { func ->
try {
(FunctionStubBuilder(context, func).build() as List<FunctionStub>).single()
} catch (e: Throwable) {
null
}
}.filterNotNull()
val fields: List<PropertyStub?> = def.fields.map { field ->
try {
assert(field.name.isNotEmpty())
@@ -179,18 +190,33 @@ internal class StructStubBuilder(
val annotation = AnnotationStub.CStruct.VarType(def.size, def.align).takeIf {
context.generationMode == GenerationMode.METADATA
}
val companion = ClassStub.Companion(
companionClassifier,
superClassInit = companionSuperInit,
annotations = listOfNotNull(annotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion)
)
var classMethods: List<FunctionStub> =
def.methods
.filter { !it.isCxxInstanceMethod }
.map { func ->
try {
(FunctionStubBuilder(context, func).build() as List<FunctionStub>).single()
} catch (e: Throwable) {
null
}
}.filterNotNull()
val classFields = def.staticFields
.map { field -> (GlobalStubBuilder(context, field).build() as List<PropertyStub>).single() }
val companion = ClassStub.Companion(
companionClassifier,
superClassInit = companionSuperInit,
annotations = listOfNotNull(annotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion),
properties = classFields,
methods = classMethods
)
return listOf(ClassStub.Simple(
classifier,
origin = origin,
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
constructors = listOf(primaryConstructor),
methods = emptyList(),
methods = methods,
modality = ClassStubModality.NONE,
annotations = listOfNotNull(structAnnotation),
superClassInit = superClassInit,
@@ -628,12 +654,12 @@ internal class GlobalStubBuilder(
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val extra = BridgeGenerationInfo(global.name, mirror.info)
val extra = BridgeGenerationInfo(global.fullName, mirror.info)
context.bridgeComponentsBuilder.arrayGetterBridgeInfo[it] = extra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
@@ -647,12 +673,12 @@ internal class GlobalStubBuilder(
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val getterExtra = BridgeGenerationInfo(global.name, mirror.info)
val getterExtra = BridgeGenerationInfo(global.fullName, mirror.info)
context.bridgeComponentsBuilder.getterToBridgeInfo[it] = getterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
@@ -664,12 +690,12 @@ internal class GlobalStubBuilder(
val setter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Setter.SimpleSetter().also {
val setterExtra = BridgeGenerationInfo(global.name, mirror.info)
val setterExtra = BridgeGenerationInfo(global.fullName, mirror.info)
context.bridgeComponentsBuilder.setterToBridgeInfo[it] = setterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_setter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_setter")
PropertyAccessor.Setter.ExternalSetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.setterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
@@ -682,10 +708,10 @@ internal class GlobalStubBuilder(
kotlinType = mirror.pointedType
val getter = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyAccessor.Getter.InterpretPointed(global.name, kotlinType.toStubIrType())
PropertyAccessor.Getter.InterpretPointed(global.fullName, kotlinType.toStubIrType())
}
GenerationMode.METADATA -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global, passViaPointer = true)
}
@@ -12,6 +12,17 @@ private val StubOrigin.ObjCMethod.isOptional: Boolean
fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod &&
this.origin.isOptional
fun FunctionStub.isCxxInstanceMember(): Boolean = this.origin is StubOrigin.Function &&
this.origin.function.isCxxInstanceMethod
fun FunctionStub.qualifiedName(): String =
if (this.origin is StubOrigin.Function && !this.origin.function.isCxxMethod) {
this.origin.function.fullName
} else {
name
}
val StubContainer.isInterface: Boolean
get() = if (this is ClassStub.Simple) {
modality == ClassStubModality.INTERFACE
@@ -97,4 +108,4 @@ val StubType.underlyingTypeFqName: String
is AbbreviatedType -> underlyingType.underlyingTypeFqName
is FunctionalType -> classifier.fqName
is TypeParameterType -> name
}
}
@@ -184,7 +184,8 @@ class StubIrTextEmitter(
if (element in bridgeBuilderResult.excludedStubs) return
val header = run {
val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val parameters = (if (element.isCxxInstanceMember()) element.parameters.drop(1) else element.parameters).
joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
val typeParameters = renderTypeParameters(element.typeParameters)
val override = if (element.isOverride) "override " else ""
@@ -660,4 +661,4 @@ class StubIrTextEmitter(
"$name : ${renderStubType(it)}"
} ?: name
}
}
}
@@ -121,16 +121,20 @@ private fun Properties.putAndRunOnReplace(key: Any, newValue: Any, beforeReplace
this[key] = newValue
}
private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
private fun selectNativeLanguage(config: DefFile.DefFileConfig, hintIsCPP: Boolean = false): Language {
val languages = mapOf(
"C" to Language.C,
"C++" to Language.CPP,
"Objective-C" to Language.OBJECTIVE_C
)
val language = config.language ?: return Language.C
val lang = config.language?.let {
languages[it]
?: error("Unexpected language '${config.language}'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
} ?: Language.C
return if (lang == Language.C && hintIsCPP) Language.CPP else lang
return languages[language] ?:
error("Unexpected language '$language'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
}
private fun parseImports(dependencies: List<KotlinLibrary>): ImportsImpl =
@@ -220,8 +224,6 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
cinteropArguments.linkerOptions.value.toTypedArray()
val verbose = cinteropArguments.verbose
val language = selectNativeLanguage(def.config)
val entryPoint = def.config.entryPoints.atMostOne()
val linkerOpts =
def.config.linkerOpts.toTypedArray() +
@@ -294,7 +296,7 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
File(nativeLibsDir).mkdirs()
val outCFile = tempFiles.create(libName, ".${language.sourceFileExtension}")
val outCFile = tempFiles.create(libName, ".${library.language.sourceFileExtension}")
val logger = if (verbose) {
{ message: String -> println(message) }
@@ -451,6 +453,14 @@ internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
return tool
}
private fun isCxxOptions(opts: List<String>) : Boolean {
if (opts.size >= 2) opts.reduce args@{ prev, that ->
if (prev == "-x" && that == "c++") return@isCxxOptions true
return@args that
}
return false
}
internal fun buildNativeLibrary(
tool: ToolConfig,
def: DefFile,
@@ -462,7 +472,8 @@ internal fun buildNativeLibrary(
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
val headerFiles = def.config.headers + additionalHeaders
val language = selectNativeLanguage(def.config)
val cppOptions = isCxxOptions(def.config.compilerOpts + additionalCompilerOpts)
val language = selectNativeLanguage(def.config, cppOptions)
val compilerOpts: List<String> = mutableListOf<String>().apply {
addAll(def.config.compilerOpts)
addAll(tool.defaultCompilerOpts)
@@ -476,6 +487,7 @@ internal fun buildNativeLibrary(
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
addAll(when (language) {
Language.C -> emptyList()
Language.CPP -> if (cppOptions) emptyList() else listOf("-x", "c++")
Language.OBJECTIVE_C -> {
// "Objective-C" within interop means "Objective-C with ARC":
listOf("-fobjc-arc")
@@ -3823,6 +3823,14 @@ createInterop("leakMemoryWithRunningThread") {
it.extraOpts "-Xcompile-source", "$projectDir/interop/leakMemoryWithRunningThread/leakMemory.cpp"
}
createInterop("cppClass") {
it.defFile 'interop/cpp/cppClass.def'
}
createInterop("cppTypes") {
it.defFile 'interop/cpp/types.def'
}
if (PlatformInfo.isAppleTarget(project)) {
createInterop("objcSmoke") {
it.defFile 'interop/objc/objcSmoke.def'
@@ -4182,6 +4190,18 @@ task interop_sourceCodeStruct(type: KonanLocalTest) {
source = "codegen/intrinsics/interop_sourceCodeStruct.kt"
}
interopTest("interop_cppClass") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/cpp/cppClass.kt"
interop = 'cppClass'
}
interopTest("interop_cppTypes") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/cpp/types.kt"
interop = 'cppTypes'
}
/*
TODO: This test isn't run automatically
task interop_echo_server(type: RunInteropKonanTest) {
@@ -0,0 +1,42 @@
language = C++
compilerOpts = -std=c++14
---
class CppTest {
public:
CppTest() { ++counter; }
CppTest(const CppTest&) = default;
explicit CppTest(int i, double j = 3.14) : iPub(i + int(j + 0.5)) {}
~CppTest() { --counter; }
int iPub = 42;
virtual int foo() { return iPub; }
static int counter;
static int s_fun() { return counter; }
class Nested {
public:
int nestedFoo() { return -2; }
} nested;
// not supported yet
operator CppTest::Nested() const;
template <class X> void fooTmplMember() const;
private:
CppTest* funPrivate() const;
static int s_funPrivate();
int iPriv;
};
int CppTest::counter;
CppTest retByValue(CppTest* s) {
return s ? *s : CppTest();
}
@@ -0,0 +1,133 @@
import kotlinx.cinterop.*
import kotlin.test.*
import cppClass.*
class FeatureTest {
@Test fun ctorDefault() {
memScoped {
val x = alloc<CppTest>()
CppTest.__init__(x.ptr)
assertEquals(42, x.iPub)
assertEquals(42, x.foo())
// dtor is not called, leak is intentional for the purpose of UT
}
}
@Test fun ctorWithParam() {
memScoped {
val x = alloc<CppTest>()
CppTest.__init__(x.ptr, 10, 3.8)
assertEquals(14, x.iPub)
}
}
@Test fun copyCtor(y: CppTest) {
val x = nativeHeap.alloc<CppTest>() {}
CppTest.__init__(x.ptr, y.ptr)
assertEquals(y.iPub, x.iPub)
nativeHeap.free(x)
}
/*
@Test fun reinitWithCtorAndDtor(y: CppTest) {
val count = CppTest.getCount()
val x = nativeHeap.alloc< CppTest>() {}
CppTest.__init__(x.ptr, y.ptr)
assertEquals( CppTest.getCount(), count + 1)
assertEquals(y.iPub, x.iPub)
CppTest.__destroy__(x.ptr)
y.iPub = -11
assertEquals(y.iPub, -11)
CppTest.__init__(x.ptr, y.ptr)
assertEquals(x.iPub, -11)
CppTest.__destroy__(x.ptr)
assertEquals( CppTest.getCount(), count)
nativeHeap.free(x)
}
@Test fun publicField(x : CppTest) {
x.iPub = 21
assertEquals(22, x.foo(x.ptr))
}
@Test fun lvRefParameter() {
memScoped {
val x = alloc<ns__NoName>()
var i = alloc<IntVar>()
i.value = 758
assertEquals(x.noNameMember(i.ptr), 759)
assertEquals(i.value, 759)
}
}
@Test fun staticField() {
val save = CppTest.getCount()
assertEquals( CppTest.getCount(), CppTest.counter)
CppTest.counter = 654
assertEquals( CppTest.getCount(), 654)
assertEquals( CppTest.getCount(), CppTest.counter)
CppTest.counter = save
assertEquals( CppTest.getCount(), save)
}
*/
}
fun main() {
val testRun = FeatureTest()
testRun.ctorDefault()
testRun.ctorWithParam()
val a0 = retByValue(null)
println("a0.useContents {iPub} = ${a0.useContents {iPub}}" )
println("a0.useContents { foo() } = ${a0.useContents { foo() }}" )
// retByValue(null)!!.getValue().foo()
// val a1 = interpretPointed<CppTest>(retByValue(null).rawValue)
// println(a1.foo())
/*
val a1 = interpretPointed< CppTest>(ns__create().rawValue)
testRun.publicField(a1)
testRun.staticField()
testRun.lvRefParameter()
a1.iPub = 112
testRun.copyCtor(a1)
testRun.reinitWithCtorAndDtor(a1)
*/
}
/*
fun testStatic() {
println(" CppTest.s_fun() returns ${ CppTest.s_fun()}")
println(" CppTest.s_fun() returns ${ CppTest.s_fun()}")
println(" CppTest.s_fun() returns ${ CppTest.s_fun()}")
}
fun testCtor() {
println("testCtor")
val cxx = nativeHeap.alloc< CppTest>() {
memcpy(ptr, ns__create(), typeOf< CppTest>().size.convert()) // use placement new here
}
cxx.foo(null)
nativeHeap.free(cxx)
}
fun test2() {
println("test2")
val x = ns__bar(null)
// val theS = interpretPointed< CppTest>(ns__bar(null).rawValue)
// theS.foo(null)
println("x.useContents {iPub} = ${x.useContents {iPub}}" )
}
*/
@@ -0,0 +1,24 @@
language = C++
compilerOpts = -std=c++14
package = cpptypes
---
class CppTest {
public:
explicit CppTest(int m) : n(m) {}
int n = 11;
virtual int get() const { return n; }
virtual void set(int m) { n = m; }
};
CppTest retByValue(int k) { return CppTest(k); }
CppTest* retByPtr(int k) { return new CppTest(k); } // Leak is intentional
CppTest const* retByPtrConst(int k) { return new CppTest(k); } // Leak is intentional
CppTest& retByRef(int k) { return * new CppTest(k); } // Leak is intentional
CppTest const& retByRefConst(int k) { return * new CppTest(k); } // Leak is intentional
int paramByValue(CppTest x) { return x.get(); }
int paramByPtr(CppTest* x) { return x? x->get() : 0; }
int paramByPtrConst(CppTest const* x) { return x? x->get() : 0; }
int paramByRef(CppTest& x) { return x.get(); }
int paramByRefConst(CppTest const& x) { return x.get(); }
@@ -0,0 +1,93 @@
import kotlinx.cinterop.*
import kotlin.test.*
import kotlin.random.*
import cpptypes.*
@Test
fun test_retByValue(k: Int) {
memScoped {
val x: CppTest = retByValue(k).getPointer(memScope).pointed
assertEquals(k, x.get())
}
}
@Test
fun test_retByPtr(k: Int) {
val x = interpretPointed<CppTest>(retByPtr(k).rawValue)
assertEquals(k, x.get())
}
@Test
fun test_retByPtrConst(k: Int) {
val x = interpretPointed<CppTest>(retByPtrConst(k).rawValue)
assertEquals(k, x.get())
}
@Test
fun test_retByRef(k: Int) {
val x = interpretPointed<CppTest>(retByRef(k).rawValue)
assertEquals(k, x.get())
}
@Test
fun test_retByRefConst(k: Int) {
val x = interpretPointed<CppTest>(retByRefConst(k).rawValue)
assertEquals(k, x.get())
}
@Test
fun test_paramByValue(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
CppTest.__init__(x.ptr, k)
assertEquals(k, paramByValue(x.readValue()))
nativeHeap.free(x)
}
@Test
fun test_paramByPtr(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
CppTest.__init__(x.ptr, k)
assertEquals(k, paramByPtr(x.ptr))
nativeHeap.free(x)
}
@Test
fun test_paramByPtrConst(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
CppTest.__init__(x.ptr, k)
assertEquals(k, paramByPtrConst(x.ptr))
nativeHeap.free(x)
}
@Test
fun test_paramByRef(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
CppTest.__init__(x.ptr, k)
assertEquals(k, paramByRef(x.ptr))
nativeHeap.free(x)
}
@Test
fun test_paramByRefConst(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
CppTest.__init__(x.ptr, k)
assertEquals(k, paramByRefConst(x.ptr))
nativeHeap.free(x)
}
fun main() {
val seed = Random.nextInt()
val r = Random(seed)
test_retByValue(r.nextInt())
test_retByPtr(r.nextInt())
test_retByPtrConst(r.nextInt())
test_retByRef(r.nextInt())
test_retByRefConst(r.nextInt())
test_paramByValue(r.nextInt())
test_paramByPtr(r.nextInt())
test_paramByPtrConst(r.nextInt())
test_paramByRef(r.nextInt())
test_paramByRefConst(r.nextInt())
}
+127
View File
@@ -0,0 +1,127 @@
Что я хотел, и что из этого вышло
--------------------------------------
Я думал, что быстро прикостыляю базовый С++ к cinterop, используя plain C обёртки над CXXMethod и передавая адрес объекта (receiver) дополнительным (скрытым) параметром. Так сделан C++ runtime и так сделана куча биндингов в C++ из разных языков. Так что - должно получиться.
Вышло сплошное разочаровние. Если обобщить все встреченные беды - вывод такой: существующий cinterop не годится для этого от слова "совсем". Что обидно: если бы я не тратил время на борьбу с cinterop - я бы, наверное, за это время написал прототип с нуля. Делать глубокий рефакторинг в рамках тестового задания я счёл неправильным - я не имею возможности даже обсудить грабли, тем более - протестить то что получится. (на самом деле, бОльшую часть кода и инфраструктуры cinterop можно переиспользовать, но рефаторинг коснётся всех сценариев - так что надо всё тестить)
Что получилось в итоге. Ну я всё же прикостылял базовый C++, не всё и с разной степенью проработки (C++ бесконечен, see below for details). Можно запустить тесты. Можно попытаться затащить библиотеку или, например, свои собственные legacy наработки. Я пытаюсь игнорировать неподдержанные (пока) типы и конструкции, но не все учтены, и на реальном примере придется подхачить C++, чтобы пройти компиляцию. Но это не проблема: дописать поддержку недостающего - вопрос времени/эффортов, принципиальных проблем нет. (В сторону: интероп с собственным legacy кодом я вижу как, потенциально, основной business case. У меня много знакомых плюсеров, которые сидят на легаси огромного объёма и были бы счастливы патчить бизнес-логику на более динамичном и функциональном языке).
Но пока результат разочаровывает, и вот почему:
* Этим неудобно пользоваться из-за разницы между CValue<CxxClass> и сгенерённым котлиновским CxxClass. (Никто и не пользуется: даже Indexer.kt всюду использует CValue, а сгенерённые классы остались без дела)
* cinterop всё время пытается делать свои копии нативных структур: readValue, useContents, placeTo etc. Это не работает для нетривиальных объектов. Хуже того: это абсолютный тупик для move семантики, которая в современном C++ является основным трендом. Пример: сейчас модно писать API, в котором все std::string передаются by value, в расчете на move. При попытке оттащить string в какую-то "другую" память (даже с использованием правильных move/copy конструкторов) я получаю маленький объект, из которого торчит указатель в C++ heap, причём указатель будет всё время меняться, immutability ему противопоказана. Или std::shared_ptr ... в общем, понятно. И. наконец, эти копирования не добавляют ничего полезного в случае kotlin-native (или я не понял).
* Похоже, cinterop был заточен на то, что основной workflow такой: аллоцировать в котлине memory block, инициализировать поля вручную, дальше побитово копировать туда где нужно. У объектного языка другой flow: get handler (using create or new), keep it and use, destroy. Я прикостылял placement new, чтобы сконструировать объект там где его аллоцировал котлин, но смысла в этом ноль. И это не работает для Factory, у объекта может не быть публичного конструктора.
* Я не стал фиксить by value параметры и return, это будет глючить на нетривиальных объектах (в тестах таких нет). Фикс простой, но концептуальный и задевает весь существующий cinterop.
Что я предлагаю сделать:
* Отказаться от попыток менеджерить нативные объекты. Это невозможно для объектов и не имеет смысла для kotlin-native. Используем handler с нативным указателем внутри. Котлиновский класс сейчас так и генерится - отлично. Никаких CValue. Параметр by value - компилятор подставит звёздочку, *rawPtr. Возврат by value: я использую placement new и copy ctor, чтобы заставить C++ вернуть значение не на стеке, а в хипе. Собственно, это сделано. Но дальше надо в котлин отдать handler с указателем в хип, а не readValue. Это тривиально и это я тоже сделал, но это ломает совместимость с существующим кодом.
* Упростить сгенерённый для котлина API, в смысле типов. Никаких CValue, ptr и прочего. Все параметры и возвращаемые типы - только котлиновский прокси класс, handler. Компилятор подставит нужное преобразование в значение, указатель или ссылку. Где-то здесь придётся покувыркаться с move и с temporary, но это feasible. Манглингом или типами показать разницу между const-nonconst параметрами (in/out). Разницы между by value и const ref вообще нет. В случае неоднозначности (конфликт имён из-за того, что не показываем в котлине разницу между by value и by ptr), и только в этом случае - дополнительно манглинг, или программист подскажет компилятору как-то, например напишет ptr.
* Вместо бесполезного для деструкторов mamscope сделать cxxscope, чтобы автоматически вызывать зарегистрированные деструкторы. Я попробовал сделать, используя std::unique_ptr - но не закончил.
TODO
----
0. Design style. Костыли. Во многих местах в реальной продуктовой задаче я бы предпочёл сначала сделать подготовительный рефакторинг, цикл review-commit-tests, потом поверх этого сделать фичу. Т.е. разделить коммит на два: изменение (без добавления) и добавление (без изменений). Для тестового задания я в основном выбирал вариант минимальных изменений, чтобы проще ревьюить за один раз. В результате - костыли и рост технического долга, но это осознанный выбор.
0. Сейчас могут сгенериться имена с конфликтами в двух популярных случаях: 1) const и non-const метод с одинаковой сигнатуройг 2) функции с похожей сигнатурой, одна получает параметр по указателю, другая по ссылке (оба биндятся как указатель). В первом случае: добавим mangling, во втором надо мапить LVReference в not nullable, и всё развяжется. Руки не дошли.
0. NativeIndex имеет плоскую структуру создаваемых сущностей. Хотелось иметь дерево, так проще поддержать вложенные классы и неймспейсы. Не успел. В результате вложенные структуры я сделал простым манглингом (quick and dirty). Это работает, но выглядит ужасно, надо переделать. В качестве варианта я рассматривал идею: оставить indexer плоским, но пусть он содержит только один уровень семантической вложенности (один уровень semantic parent в терминологии clang), и я буду строить дерево из множества маленьких одноуровневых индексеров.
0. Хотелось замапить namespace в package. Вроде просто, но уткнулся в тяжелый рефакторинг в генераторах: там единственность выходного файла и package прибита гвоздями. Один translation unit = один выходной файл. А тут translation unit должен создать целое дерево с перекрестными импортами.
0. Многие типы и конструкции не поддержаны, но каких-то принципиальных проблем в реализации я не вижу. Темплейты - почти мапятся в темплейты, а инстанцированный темплейт - это обычный класс. Exceptions надо просто ретранслировать. RValueRef кажется должен работать легко. Операторы по разному... их много и это надолго. Можно мапить в обычные функции. Но например operator-> я бы предложил не мудрить, а вместо этого написать удобный аналог unique_ptr и shared_ptr вручную, и тогда их почти не останется. Как мапить лямбду в блок и обратно, похоже без поддержки рантайма не обойдется. И т.д., фичи бесконечны...
0. Override С++ методов в котлине (callbacks) Можно сделать через обертки: генерим C++ класс-наследник, у которого каждый метод перевызывает соответствующий котлинвский метод как plain C. Это сработает, но сгенерим и скомпилируем много лишнего. Хорошо бы генерить эти обертки только on demand, т.е. когда компилятор видит, что котлин хотел бы перегрузить соответствующий метод. Но замечу, что в современном C++ для колбэка скорее будут использовать лямбду и std::function, чем наследование.
0. Надо замапить самые важные std типы: string, containers, smart pointers
0. Сейчас работа с C/C++ из котлина выглядит неестественно, многословная. Interop должен быть seamless. Без лишних interpretPointed, rawValue etc. Но это требует определенного пересмотра концепции (type matching). Уже сказал об этом выше.
0. Костыль visitChildren, или франкенштейн из двух параллельных indexDeclaration.
Проблема в том, что IndexDeclaration(CXIdxDeclInfo) не отличает класс от темплейта (и функцию от темплейтной функции). Курсор внутри дерева indexDeclaration для темплейтов имеет kind = CXCursor_ClassDecl (или StructDecl) вместо ожидаемого CXCursor_ClassTemplate, и я не нашел способа вытащить "правильный" курсор. Но если разобрать дерево через visitChildren - то курсор правильный. Поэтому, если язык C++, то я разбираю классы через visitChildren (и отбрасываю темплейты, пока они не поддержаны). Я временно оставил такого франкенштейна, когда часть сущностей парсится через CXIdxDeclInfo (из clang_indexTranslationUnit), а часть через CXCursor (из visitChildren). Я думаю, что надо всё перенести в visitChildren, но у меня не было возможности протестить все сценарии в plain C и ObjC. Такой рефакторинг - не для тестового задания. Кстати, такой костыль уже был до меня, для парсинга ObjC, но я не стал втыкать свой код в него, а написал отдельный - потому что мне нужен был CXChildVisit_Recurse, а там был CXChildVisit_Continue и я опять боялся сломать ObjC, который не умею как следует протестить.
Conceptual
----------
0. Internal linkage.
Сейчас сущности с internal linkage (static & anon namespace) не фильтруются, биндинг на них создаётся (все функции в .def в тестах написаны со словом static). Фактически, строим так, как будто C включён в котлиновский исходник. В таком случае, эти сущности должны биндиться как internal, причем каждый С-ник надо компилировать, как отдельный translation unit, иначе будут конфликты.
- Это нормальный подход, если моё намерение - расширить (extend) нативную имплементацию на котлине.
- Если моё намерение - затащить нативную библиотеку, то я компилирую все публичные хедеры как один translation unit, при этом лучше зафильтровать static.
- Проблема некритична, потому что в публичных хедерах static и anon namespace не встречается. Зато часто в c/cpp имплементации. Лучше быть последовательным в этом вопросе.
0. Что делать с protected и private? Я пока выключил. Технически, сейчас котлиновский класс соответствует C++ ссылке на объект, а не derived class, т.е. должен иметь доступ только к public. IMHO: это неправильно, в такой логике невозможно реализовать C++ callbacks (оверрайдить виртуальные методы, в т.ч. приватные). See below.
0. Derive vs Use. Хотелось бы имитироватиь наследование от C++ класса. Это можно сделать в концепции kotlin object is a handler, через делегирование, но надо решить вопрос автоматического вызова деструктора на созданном объекте.
0. Конструкторы-деструкторы надо доработать. Вопрос концептуальный, требует обсуждения. И это довольно большая работа. Моё предложение: надо различать 3 сущности:
- Явно аллоцированный объект с владением: alloc + init (т.е. placement new) и парный к нему destroy (это this->~MyClass()), деаллокация автоматическая или ручная
- Holder, владеющий указатель на объект, созданный в нативном хипе по new MyClass() и парный к нему delete. Аналог std::unique_ptr. Семантически не отличается от предыдущего.
- Non-owning holder без передачи ownership, т.е. невладеюший указатель. Освобождать не надо. Аналог C raw pointer.
- Можно замапить std::shared_ptr на котлин тип с acquire/release парой.
Design issues
-------------
0. В контейнере members может лежать IncompleteField. Это имело бы смысл, если Incomplete - такой lazy тип, который позже можно, например, зарезолвить. Но это не предусмотрено - так что зачем добавлять dummy, надо было просто пропустить. C FunctionDecl ещё хуже: в registry складываем неподдерживаемые функции, их отличие только в том, что у них может быть UnsupportedType у параметра. В результате exception в stub builder'е. Я заменил такие невалидные объекты на null, их легче потом фильтровать. Но вообще-то их добавлять не следовало. Проблема в том, как сделано добавление: через getOrPut. Если я только в середине построения объекта понял, что валидный объект создать не получается - он всё равно уже добавлен в map, я уже внутри initializer'а. И это не эксепшн концептуально, потому что это нормальная часть контракта, не исключение. Здесь лучше подошла бы логика factory: пытаюсь создать объект и добавляю только если создался успешно.
0. Часто приватный метод класса является, по сути, чистой функцией и не работает со стейтом объекта - только с параметром. Такие функции надо вытаскивать из класса и делать чистой функцией, или экстеншном на типе параметра. Пример:. getArrayLength(type: ArrayType) сделана приватным методом StructStubBuilder'а, но никакого отношения к этому классу не имеет. Таких примеров много.
0. Спорное решение про throw Error("Native interop types constructors must not be called directly"). В чем смысл? Мой тип - handler, и это его конструктор. Точно так работает `std::make_unique` и `make_shared`, при этом я могу и конструктором воспользоваться: `auto x = unique_ptr<T>(new T())`. Другого способа сделать биндинг на класс, у которого только factory method - вообще нет. interpretPointed по сути то же самое, но непонятно, и зачем плодить сущности? Наконец, это runtime exception, а не синтаксическая ошибка, которую мог бы подсветить IDE - и это неудобно.
В порядке эксперимента я отключил этот error, в результате и клиентский код, и генеренный стал проще и читабельнее. От чего защищаемся, ведь я все равно могу написать такой wrapper вручную, хранить как long то, что вернул фактори метод, а потом вызывать методы класса на нём? Пусть лучше это сделает генератор.
0. Parameter by value.
На входе - C declaration:
```
struct CTest {};
int paramByValue(struct CTest x);
```
На выходе
```
fun paramByValue(x: CValue<CTest>): Int {
memScoped {
return kniBridge0(x.getPointer(memScope).rawValue)
}
}
int32_t c1_kniBridge0 (void* p0) {
return (int32_t)paramByValue(*(struct CTest*)p0);
}
```
Здесь копия сделана дважды: сначала CValue.getPointer, а потом при вызове paramByValue. Причем во втором случае C++ компилятор корректно использует конструктор копирования, а getPointer - увы...
Нет, это еще не всё. Поскольку функция принимает CValue, а у меня в руках есть только `class CTest`, который `CStructVar`, то придется сделать третье копирование уже в клиентском коде: readValue.
При возврате то же самое:
```
void c1_kniBridge1 (void* p0) {
struct CTest kniStructResult = retByValue();
memcpy(p0, &kniStructResult, sizeof(kniStructResult));
}
```
Здесь уже два копирования. Достаточно было бы
```
void c1_kniBridge1 (void* p0) {
*(struct CTest*) p0 = retByValue();
}
```
(Я знаю, что это не сработает при наличии const fields, но это значит, что автор явно запретил копирование. Такую структуру на C невозможно создать в хипе, только на стеке - такой "тёмный угол" стандарта C. На C++ это решается с помощью placement new.)
Но есть ещё и третье копирование в котлиновской обёртке:
```
return kniRetVal.readValue()
```
и это ещё не конец: kniRetVal это CVariable, так что readValue делает копию дважды. Четыре копирования на каждый вызов retByValue - только чтобы получить экземпляр структуры.
0. Я попытался постичь дзен этого пассажа:
```
* [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.
```
Первые две фразы - ok. Третью я не смог расшифровать. `Passing [CValues]` - куда passing? Если C API принимает MyStruct* то это мутатор с input параметром и отдавать туда нужно указатель на исходный объект. Иначе в чём смысл - вызывать мутатор на временном объекте? Если параметр MyStruct const* то компилятор уже позаботился о нас (трюки типа mutable или const_cast не в счёт - тот кто делает это в публичном API - сам берет на себя ответственность). Если параметр по значению, то C компилятор и так сделает копию.
+8
View File
@@ -0,0 +1,8 @@
Build native:
g++ -std=c++14 -o src/native/features.o -c src/native/features.cpp
c++ interop:
../../dist/bin/cinterop -def features.def -compiler-options "-I." -o features
@@ -0,0 +1,6 @@
headers = src/native/features.h
headerFilter = src/native/features.h
package = test
language = C++
#compilerOpts = -std=c++14
@@ -0,0 +1,176 @@
//package test.cpp
import kotlinx.cinterop.*
import platform.posix.*
import platform.posix.memcpy
import test.*
import kotlin.test.*
class FeatureTest {
@Test fun ctorDefault() {
memScoped {
val x = alloc<ns__CppTest>()
ns__CppTest.__init__(x.ptr)
assertEquals(42, x.iPub)
assertEquals(42, x.foo(null))
assertEquals(43, x.foo(x.ptr))
// dtor is not called, leak is intentional for the purpose of UT
}
}
@Test fun ctorWithParam() {
memScoped {
val x = alloc<ns__CppTest>()
ns__CppTest.__init__(x.ptr, 1001, 0.0)
assertEquals(1001, x.iPub)
assertEquals(1001, x.foo(null))
assertEquals(1002, x.foo(x.ptr))
}
}
@Test fun copyCtor(y: ns__CppTest) {
val x = nativeHeap.alloc<ns__CppTest>() {}
ns__CppTest.__init__(x.ptr, y.ptr)
assertEquals(y.iPub, x.iPub)
nativeHeap.free(x)
}
@Test fun reinitWithCtorAndDtor(y: ns__CppTest) {
val count = ns__CppTest.getCount()
val x = nativeHeap.alloc<ns__CppTest>() {}
ns__CppTest.__init__(x.ptr, y.ptr)
assertEquals(ns__CppTest.getCount(), count + 1)
assertEquals(y.iPub, x.iPub)
ns__CppTest.__destroy__(x.ptr)
y.iPub = -11
assertEquals(y.iPub, -11)
ns__CppTest.__init__(x.ptr, y.ptr)
assertEquals(x.iPub, -11)
ns__CppTest.__destroy__(x.ptr)
assertEquals(ns__CppTest.getCount(), count)
nativeHeap.free(x)
}
@Test fun publicField(x : ns__CppTest) {
x.iPub = 21
assertEquals(22, x.foo(x.ptr))
}
@Test fun lvRefParameter() {
memScoped {
val x = alloc<ns__NoName>()
var i = alloc<IntVar>()
i.value = 758
assertEquals(x.noNameMember(i.ptr), 759)
assertEquals(i.value, 759)
}
}
@Test fun staticField() {
val save = ns__CppTest.getCount()
assertEquals(ns__CppTest.getCount(), ns__CppTest.counter)
ns__CppTest.counter = 654
assertEquals(ns__CppTest.getCount(), 654)
assertEquals(ns__CppTest.getCount(), ns__CppTest.counter)
ns__CppTest.counter = save
assertEquals(ns__CppTest.getCount(), save)
}
}
fun main() {
val testRun = FeatureTest()
testRun.ctorDefault()
testRun.ctorWithParam()
val a1 = interpretPointed<ns__CppTest>(ns__create().rawValue)
testRun.publicField(a1)
testRun.staticField()
testRun.lvRefParameter()
a1.iPub = 112
testRun.copyCtor(a1)
testRun.reinitWithCtorAndDtor(a1)
//******************************
println("*** UT passed ***")
//******************************
testStatic()
testCtor()
// testCtor1()
// testCtor2()
// testCtor3()
test2()
}
fun testStatic() {
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
}
fun testCtor() {
println("testCtor")
val cxx = nativeHeap.alloc<ns__CppTest>() {
memcpy(ptr, ns__create(), typeOf<ns__CppTest>().size.convert()) // use placement new here
}
cxx.foo(null)
nativeHeap.free(cxx)
}
/*
fun testCtor1() {
println("testCtor1: interpretPointed<ns__CppTest>(ns__CppTest.__create__().rawValue)")
val theStruct = interpretPointed<ns__CppTest>(ns__CppTest.__create__().rawValue)
theStruct.iPub = 33
theStruct.foo(theStruct.ptr)
println("testCtor1: ns__CppTest(ns__CppTest.__create__().rawValue)")
val xs = ns__CppTest(ns__CppTest.__create__().rawValue)
xs.foo(null)
xs.foo(xs.ptr)
println("testCtor1: ns__CppTest(ns__CppTest.__create__(1001).rawValue)")
val x2 = ns__CppTest(ns__CppTest.__create__(1001, 2.718).rawValue)
x2.foo(null)
x2.foo(x2.ptr)
}
fun testCtor2() {
println("testCtor2: ns__CppTest(ns__create().rawValue)")
val xs = ns__CppTest(ns__create().rawValue)
xs.foo(null)
xs.foo(xs.ptr)
}
fun testCtor3() {
println("testCtor3: MyStruct()")
val xs = MyStruct()
xs.foo()
}
*/
fun test2() {
println("test2")
val x = ns__bar(null)
// val theS = interpretPointed<ns__CppTest>(ns__bar(null).rawValue)
// theS.foo(null)
println("x.useContents {iPub} = ${x.useContents {iPub}}" )
}
@@ -0,0 +1,73 @@
#include "features.h"
#include <iostream>
using namespace std;
namespace ns {
int NoName::noNameMember(int& iRef) {
cout << __PRETTY_FUNCTION__ << " invoked" << endl;
return ++iRef;
}
int CppTest::counter;
int CppTest::s_fun() {
static int counter = 777;
cout << __PRETTY_FUNCTION__ << " invoked" << endl;
return counter++;
}
int CppTest::foo(const CppTest* x) {
int res = x == this;
cout << "This is CppTest::foo: result is iPub + (int)(param == this): " << iPub + res << endl;
return iPub + res;
}
CppTest::CppTest() {
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest::CppTest(const CppTest& c) {
*this = c;
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest::CppTest(int i, double j) : iPub(i) {
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest::~CppTest() {
cout << --counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest bar(CppTest* s) {
if (s)
return *s;
else
return * new CppTest();
}
CppTest* create() {
return new CppTest();
}
} // ns
CppTest* create() {
cout << __PRETTY_FUNCTION__ << " declared in global ns" << endl;
return nullptr;
}
::CppTest* ns2::create() {
cout << __PRETTY_FUNCTION__ << " declared in ns2" << endl;
return nullptr;
}
void test() {
char buf[sizeof(ns::CppTest)];
ns::CppTest* x = new((ns::CppTest*)buf) ns::CppTest();
}
@@ -0,0 +1,211 @@
class MyClass {
};
MyClass retByValue();
struct OuterCStruct {
const int plainCField;
struct {
int innerCField;
};
union {
int innerUnionF1;
double innerUnionF2;
};
struct Inner {
};
struct Inner innerImpl;
#ifdef __cplusplus
// Illegal C. ok in C++
typedef struct {
} NestedStructInnerT;
NestedStructInnerT nestedStructT;
#endif
};
const int g_IntVal = 0;
typedef int (*funT)(int);
void fooFuncParam( funT );
void barFuncParam( int (*)(int) );
template <typename T> struct TmplStruct {
public:
void baz() const {}
};
void funTmplParam(TmplStruct<int>& t);
template <class X> void tmplFunction();
template <class X> void tmplFunction(X x);
//int funVariadic(const char* format, ...);
//#include <cstdarg>
//void appendVAList(const char format[], va_list); // doesn't work
int plainCFreeFunction();
static inline void plainCInternalFunction(); // [Conceptual] should be mapped as internal fun or not eligible for binding at all
int g_Var;
struct PlainCStruct {
int plainCField;
struct {
int innerCField;
};
};
typedef struct PlainCStruct PlainCStructT;
using PlainCStructAlias = PlainCStruct;
void wrappingFun() {
class NestedInFunction {
int varInLocalClass;
} var; // local types shall be ignored
}
struct UnknownT;
UnknownT* cFunUnknownParams(const UnknownT*);
struct ForwardT; // declaratuin
ForwardT* cFunForwardParams(const ForwardT*); // decl
struct ForwardT {}; // definition
ForwardT* cFunForwardParams(const ForwardT*) { // def
return new ForwardT();
}
namespace ns0 {
class clDeclaredInNS;
extern int varWithDefinition;
int varWithDefFirst = 99;
}
namespace ns0_alias = ns0;
class ns0_alias::clDeclaredInNS{};
extern int ns0_alias::varWithDefFirst;
using clDeclaredInNSAlias = ns0_alias::clDeclaredInNS;
// int ns0_alias::varInNSAlias; // illegal declaration
int ns0_alias::varWithDefinition = 21;
namespace ns {
int g_varInNS;
namespace {
void funInInnerAnonNS();
namespace nsInAnonNS {
int memberOf_nsInAnonNS;
}
}
namespace NestedNS {
int funInNestedNS();
int g_varInNestedNS;
}
namespace {
void funInInnerAnonNS();
}
typedef class {
public:
int noNameMember(int& iRef);
// int noNameMember(const int& iRef); need mangling
static int noNameStaticFun(); // won't work
int fieldInAnonClass;
// static int s_fieldInAnonClass; not legal C++
} NoName;
enum EnumInNS {one, two, three};
enum class EnumClass : char { one, two, three};
class CppTest {
public:
static int s_fun();
CppTest();
CppTest(const CppTest&);
explicit CppTest(int i, double j = 3.14);
~CppTest();
operator NoName() const;
int iPub = 42;
virtual int foo(const CppTest*);
// virtual int foo(const CppTest*) const;
static int counter;
static NoName compStaticField;
NoName compField;
static int getCount() { return counter; }
template <class X> void fooTmplMember() const;
class Nested {
public:
int nestedFoo();
};
enum NestedEnum {one, two, three};
void funEnumParam(NestedEnum e);
private:
CppTest* funPrivate() const;
static int s_funPrivate();
private:
int iPriv;
};
CppTest funRetByValue(CppTest* s);
CppTest* create();
} // ns
namespace {
ns::CppTest* fooInAnonNamespace();
}
class CppTest;
CppTest* create();
namespace ns2 {
::CppTest* create();
} // ns2
/*
template <typename T> class TmplClass {
public:
void baz() const {}
};
typedef class TmplStruct<int> IntTmplStruct;
IntTmplStruct intTmplStruct;
struct Smth {
IntTmplStruct intTmplStruct;
};
*/
+6
View File
@@ -0,0 +1,6 @@
g++ -std=c++14 main.cpp -I$HOME/work/cpptools/skia /Users/vdi/work/cpptools/skia/out/Static/libskia.a -framework CoreServices -framework CoreText -framework CoreGraphics
../../dist/bin/cinterop -def Skia.def -o Skia -compiler-options "-I/Volumes/vdi/work/cpptools/skia"
../../dist/bin/kotlinc src/skiaMain/kotlin/test_SkString.kt -o test_SkString -l Skia -linker-options "$HOME/work/cpptools/skia/out/Static/libskia.a" -linker-options "-framework CoreServices" -linker-options "-framework CoreText" -linker-options "-framework CoreGraphics"
+5
View File
@@ -0,0 +1,5 @@
headers = include/core/SkString.h include/core/SkTime.h
headerFilter = include/core/SkTime.h include/core/SkString.h
package = skia
compilerOpts = -std=c++14
compilerOpts = -x c++
+11
View File
@@ -0,0 +1,11 @@
#include "include/core/SkTime.h"
#include <iostream>
using namespace std;
int main()
{
auto nsec = SkTime::GetNSecs();
cout << nsec << endl;
return 0;
}
@@ -0,0 +1,19 @@
import kotlinx.cinterop.*
import SkTime.*
fun main() {
println("Skia sample")
println("${SkTime.GetNSecs()}")
println("${SkTime.GetNSecs()}")
println("${SkTime.GetNSecs()}")
test_GetDateTime()
}
fun test_GetDateTime() {
memScoped {
val dateTime = alloc<SkTime__DateTime>()
SkTime.GetDateTime(dateTime.ptr)
println("${dateTime.fYear}-${dateTime.fMonth}-${dateTime.fDay} ${dateTime.fHour}:${dateTime.fMinute}.${dateTime.fSecond}" )
}
}
@@ -0,0 +1,80 @@
import kotlinx.cinterop.*
import kotlin.test.*
import skia.*
fun hello() {
memScoped {
val cString = "Hello Skia".cstr.getPointer(memScope)
val s = alloc<SkString>() {
SkString.__init__(ptr, cString)
}
println(s.c_str()?.toKString())
}
}
fun SkString_create(from: String): SkString {
val res = nativeHeap.alloc<SkString>() {}
memScoped {
val cString = from.cstr.getPointer(memScope)
SkString.__init__(res.ptr, cString)
}
return res
}
fun SkString.clone(): SkString {
return nativeHeap.alloc<SkString>() {
SkString.__init__(ptr, this@clone.ptr)
}
}
fun SkString.delete(): Unit {
SkString.__destroy__(this.ptr)
nativeHeap.free(this)
}
fun SkString.toKString() = this.c_str()!!.toKString()
fun main() {
hello()
/*
with (CppContext) {
val go = SkString("Let's go fishing!")
val goback = SkString(go)
val pos = goback.find("fish")
println(go)
if (pos >= 0) {
goback.insert(pos, "back!")
goback.resize((pos + "back!".length)
}
println(goback)
oback.swap(go)
goback.resize(goback.size() - 1U)
goback.append(" again!")
println(goback)
}
*/
val go = SkString_create("Let's go fishing!")
println(go.toKString())
val goback = go.clone()
memScoped {
val pos = goback.find("fish".cstr.getPointer(memScope))
if (pos >= 0) {
goback.insert(pos.convert<ULong>(), "back!".cstr.getPointer(memScope))
goback.resize((pos + "back!".length).convert<ULong>())
}
println(goback.toKString())
goback.swap(go.ptr)
goback.resize(goback.size() - 1U)
goback.append(" again!".cstr.getPointer(memScope))
println(goback.toKString())
}
go.delete()
goback.delete()
}