[Kotlin/Native][Interop] Provide pure c wrappers over cpp for skia interop
This commit is contained in:
committed by
Alexander Gorshenev
parent
61825e9aec
commit
5f582ad28a
@@ -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.
|
||||||
|
|
||||||
+212
-19
@@ -32,6 +32,8 @@ private class StructDefImpl(
|
|||||||
size, align, decl
|
size, align, decl
|
||||||
) {
|
) {
|
||||||
override val members = mutableListOf<StructMember>()
|
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) {
|
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>()
|
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() {
|
internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean = false) : NativeIndex() {
|
||||||
|
|
||||||
private sealed class DeclarationID {
|
private sealed class DeclarationID {
|
||||||
@@ -136,10 +162,10 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
override val typedefs get() = typedefRegistry.included
|
override val typedefs get() = typedefRegistry.included
|
||||||
private val typedefRegistry = TypeDeclarationRegistry<TypedefDef>()
|
private val typedefRegistry = TypeDeclarationRegistry<TypedefDef>()
|
||||||
|
|
||||||
private val functionById = mutableMapOf<DeclarationID, FunctionDecl>()
|
private val functionById = mutableMapOf<DeclarationID, FunctionDecl?>()
|
||||||
|
|
||||||
override val functions: Collection<FunctionDecl>
|
override val functions: Collection<FunctionDecl>
|
||||||
get() = functionById.values
|
get() = functionById.values.filterNotNull()
|
||||||
|
|
||||||
override val macroConstants = mutableListOf<ConstantDef>()
|
override val macroConstants = mutableListOf<ConstantDef>()
|
||||||
override val wrappedMacros = mutableListOf<WrappedMacroDef>()
|
override val wrappedMacros = mutableListOf<WrappedMacroDef>()
|
||||||
@@ -191,6 +217,43 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
return StructDeclImpl(typeSpelling, getLocation(cursor))
|
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>) {
|
private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue<CXCursor>) {
|
||||||
val type = clang_getCursorType(cursor)
|
val type = clang_getCursorType(cursor)
|
||||||
|
|
||||||
@@ -205,17 +268,19 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
when (cursor.kind) {
|
when (cursor.kind) {
|
||||||
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
|
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
|
||||||
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
|
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
|
||||||
|
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
|
||||||
else -> error(cursor.kind)
|
else -> error(cursor.kind)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
structDef.members += fields
|
structDef.members += fields
|
||||||
|
visitClass(cursor, structDef)
|
||||||
|
|
||||||
structDecl.def = structDef
|
structDecl.def = structDef
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) {
|
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)
|
val name = getCursorSpelling(fieldCursor)
|
||||||
if (name.isNotEmpty()) {
|
if (name.isNotEmpty()) {
|
||||||
val fieldType = convertCursorType(fieldCursor)
|
val fieldType = convertCursorType(fieldCursor)
|
||||||
@@ -465,7 +530,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor))
|
convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor))
|
||||||
|
|
||||||
private inline fun objCType(supplier: () -> ObjCPointer) = when (library.language) {
|
private inline fun objCType(supplier: () -> ObjCPointer) = when (library.language) {
|
||||||
Language.C -> UnsupportedType
|
Language.C, Language.CPP -> UnsupportedType
|
||||||
Language.OBJECTIVE_C -> supplier()
|
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_Record -> RecordType(getStructDeclAt(clang_getTypeDeclaration(type)))
|
||||||
CXType_Enum -> EnumType(getEnumDefAt(clang_getTypeDeclaration(type)))
|
CXType_Enum -> EnumType(getEnumDefAt(clang_getTypeDeclaration(type)))
|
||||||
|
|
||||||
CXType_Pointer -> {
|
CXType_Pointer, CXType_LValueReference -> {
|
||||||
val pointeeType = clang_getPointeeType(type)
|
val pointeeType = clang_getPointeeType(type)
|
||||||
val pointeeIsConst =
|
val pointeeIsConst =
|
||||||
(clang_isConstQualifiedType(clang_getCanonicalType(pointeeType)) != 0)
|
(clang_isConstQualifiedType(clang_getCanonicalType(pointeeType)) != 0)
|
||||||
@@ -590,7 +655,9 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
val convertedPointeeType = convertType(pointeeType)
|
val convertedPointeeType = convertType(pointeeType)
|
||||||
PointerType(
|
PointerType(
|
||||||
if (convertedPointeeType == UnsupportedType) VoidType else convertedPointeeType,
|
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
|
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) {
|
when (kind) {
|
||||||
CXIdxEntity_Struct, CXIdxEntity_Union -> {
|
CXIdxEntity_Struct, CXIdxEntity_Union, CXIdxEntity_CXXClass -> {
|
||||||
if (entityName == null) {
|
if (entityName == null) {
|
||||||
// Skip anonymous struct.
|
// Skip anonymous struct.
|
||||||
// (It gets included anyway if used as a named field type).
|
// (It gets included anyway if used as a named field type).
|
||||||
} else {
|
} else {
|
||||||
getStructDeclAt(cursor)
|
if (library.language != Language.CPP) {
|
||||||
|
getStructDeclAt(cursor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CXIdxEntity_Typedef -> {
|
CXIdxEntity_Typedef, CXIdxEntity_CXXTypeAlias -> {
|
||||||
val type = clang_getCursorType(cursor)
|
val type = clang_getCursorType(cursor)
|
||||||
getTypedef(type)
|
getTypedef(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
CXIdxEntity_Function -> {
|
CXIdxEntity_Function -> {
|
||||||
if (isSuitableFunction(cursor)) {
|
if (isSuitableFunction(cursor)
|
||||||
|
&& library.language != Language.CPP) {
|
||||||
functionById.getOrPut(getDeclarationId(cursor)) {
|
functionById.getOrPut(getDeclarationId(cursor)) {
|
||||||
getFunction(cursor)
|
getFunction(cursor)
|
||||||
}
|
}
|
||||||
@@ -822,13 +909,15 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
}
|
}
|
||||||
|
|
||||||
CXIdxEntity_Variable -> {
|
CXIdxEntity_Variable -> {
|
||||||
if (info.semanticContainer!!.pointed.cursor.kind == CXCursorKind.CXCursor_TranslationUnit) {
|
val parentKind = info.semanticContainer!!.pointed.cursor.kind
|
||||||
// Top-level variable.
|
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)) {
|
globalById.getOrPut(getDeclarationId(cursor)) {
|
||||||
GlobalDecl(
|
GlobalDecl(
|
||||||
name = entityName!!,
|
name = entityName!!,
|
||||||
type = convertCursorType(cursor),
|
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>) {
|
fun indexObjCClass(cursor: CValue<CXCursor>) {
|
||||||
if (isAvailable(cursor)) {
|
if (isAvailable(cursor)) {
|
||||||
getObjCClassAt(cursor)
|
getObjCClassAt(cursor)
|
||||||
@@ -892,14 +1024,19 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFunction(cursor: CValue<CXCursor>): FunctionDecl {
|
private fun getFunction(cursor: CValue<CXCursor>, receiver: StructDecl? = null): FunctionDecl? {
|
||||||
val name = clang_getCursorSpelling(cursor).convertAndDispose()
|
if (!isFuncDeclEligible(cursor)) {
|
||||||
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(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) {
|
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)
|
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
|
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? {
|
private fun getObjCMethod(cursor: CValue<CXCursor>): ObjCMethod? {
|
||||||
@@ -957,6 +1127,22 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
CXAvailabilityKind.CXAvailability_NotAccessible -> false
|
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> {
|
private fun getFunctionParameters(cursor: CValue<CXCursor>): List<Parameter> {
|
||||||
val argNum = clang_Cursor_getNumArguments(cursor)
|
val argNum = clang_Cursor_getNumArguments(cursor)
|
||||||
val args = (0..argNum - 1).map {
|
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, _ ->
|
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
|
||||||
val file = getContainingFile(cursor)
|
val file = getContainingFile(cursor)
|
||||||
if (file in headers && nativeIndex.library.includesDeclaration(cursor)) {
|
if (file in headers && nativeIndex.library.includesDeclaration(cursor)) {
|
||||||
|
|||||||
+1
-1
@@ -171,7 +171,7 @@ private fun reparseWithCodeSnippets(library: CompilationWithPCH,
|
|||||||
|
|
||||||
names.forEach { name ->
|
names.forEach { name ->
|
||||||
val codeSnippetLines = when (library.language) {
|
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() {",
|
listOf("void $CODE_SNIPPET_FUNCTION_NAME_PREFIX$name() {",
|
||||||
" __auto_type KNI_INDEXER_VARIABLE_$name = $name;",
|
" __auto_type KNI_INDEXER_VARIABLE_$name = $name;",
|
||||||
"}")
|
"}")
|
||||||
|
|||||||
+53
-4
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.native.interop.indexer
|
|||||||
|
|
||||||
enum class Language(val sourceFileExtension: String) {
|
enum class Language(val sourceFileExtension: String) {
|
||||||
C("c"),
|
C("c"),
|
||||||
|
CPP("cpp"),
|
||||||
OBJECTIVE_C("m")
|
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) {
|
abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) {
|
||||||
|
|
||||||
enum class Kind {
|
enum class Kind {
|
||||||
STRUCT, UNION
|
STRUCT, UNION, CLASS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract val methods: List<FunctionDecl>
|
||||||
abstract val members: List<StructMember>
|
abstract val members: List<StructMember>
|
||||||
|
abstract val staticFields: List<GlobalDecl>
|
||||||
abstract val kind: Kind
|
abstract val kind: Kind
|
||||||
|
|
||||||
val fields: List<Field> get() = members.filterIsInstance<Field>()
|
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)
|
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.
|
* C function declaration.
|
||||||
*/
|
*/
|
||||||
class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type, val binaryName: String,
|
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.
|
* 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 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.
|
* C type.
|
||||||
@@ -279,7 +326,9 @@ data class RecordType(val decl: StructDecl) : Type
|
|||||||
|
|
||||||
data class EnumType(val def: EnumDef) : 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.
|
// TODO: refactor type representation and support type modifiers more generally.
|
||||||
|
|
||||||
data class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type
|
data class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type
|
||||||
|
|||||||
+40
@@ -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 CXTypeKind.spelling: String get() = clang_getTypeKindSpelling(this).convertAndDispose()
|
||||||
internal val CXCursorKind.spelling: String get() = clang_getCursorKindSpelling(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 {
|
internal fun CValue<CXString>.convertAndDispose(): String {
|
||||||
try {
|
try {
|
||||||
return clang_getCString(this)!!.toKString()
|
return clang_getCString(this)!!.toKString()
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ typealias KotlinExpression = String
|
|||||||
fun String.asSimpleName(): String = if (this in kotlinKeywords || this.contains("$")) {
|
fun String.asSimpleName(): String = if (this in kotlinKeywords || this.contains("$")) {
|
||||||
"`$this`"
|
"`$this`"
|
||||||
} else {
|
} else {
|
||||||
this
|
this.replace(':', '_')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+43
-12
@@ -16,10 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.native.interop.gen
|
package org.jetbrains.kotlin.native.interop.gen
|
||||||
|
|
||||||
import org.jetbrains.kotlin.native.interop.indexer.RecordType
|
import org.jetbrains.kotlin.native.interop.indexer.*
|
||||||
import org.jetbrains.kotlin.native.interop.indexer.Type
|
|
||||||
import org.jetbrains.kotlin.native.interop.indexer.VoidType
|
|
||||||
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
|
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
|
||||||
@@ -27,7 +24,8 @@ import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
|
|||||||
*/
|
*/
|
||||||
class MappingBridgeGeneratorImpl(
|
class MappingBridgeGeneratorImpl(
|
||||||
val declarationMapper: DeclarationMapper,
|
val declarationMapper: DeclarationMapper,
|
||||||
val simpleBridgeGenerator: SimpleBridgeGenerator
|
val simpleBridgeGenerator: SimpleBridgeGenerator,
|
||||||
|
val language: Language
|
||||||
) : MappingBridgeGenerator {
|
) : MappingBridgeGenerator {
|
||||||
|
|
||||||
override fun kotlinToNative(
|
override fun kotlinToNative(
|
||||||
@@ -40,7 +38,12 @@ class MappingBridgeGeneratorImpl(
|
|||||||
): KotlinExpression {
|
): KotlinExpression {
|
||||||
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
|
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) {
|
if (type.unwrapTypedefs() is RecordType) {
|
||||||
builder.pushMemScoped()
|
builder.pushMemScoped()
|
||||||
val bridgeArgument = "$value.getPointer(memScope).rawValue"
|
val bridgeArgument = "$value.getPointer(memScope).rawValue"
|
||||||
@@ -79,6 +82,25 @@ class MappingBridgeGeneratorImpl(
|
|||||||
val unwrappedType = type.unwrapTypedefs()
|
val unwrappedType = type.unwrapTypedefs()
|
||||||
if (unwrappedType is RecordType) {
|
if (unwrappedType is RecordType) {
|
||||||
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
|
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 {
|
} else {
|
||||||
nativeValues.add(
|
nativeValues.add(
|
||||||
mirror(declarationMapper, type).info.cFromBridged(
|
mirror(declarationMapper, type).info.cFromBridged(
|
||||||
@@ -90,18 +112,27 @@ class MappingBridgeGeneratorImpl(
|
|||||||
|
|
||||||
val nativeResult = block(nativeValues)
|
val nativeResult = block(nativeValues)
|
||||||
|
|
||||||
when (unwrappedReturnType) {
|
when {
|
||||||
is VoidType -> {
|
unwrappedReturnType is VoidType -> {
|
||||||
out(nativeResult + ";")
|
out(nativeResult + ";")
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
is RecordType -> {
|
unwrappedReturnType is RecordType -> {
|
||||||
val kniStructResult = "kniStructResult"
|
val kniStructResult = "kniStructResult"
|
||||||
|
|
||||||
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
|
if (language == Language.CPP) {
|
||||||
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
|
// 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 -> {
|
else -> {
|
||||||
nativeResult
|
nativeResult
|
||||||
}
|
}
|
||||||
@@ -204,4 +235,4 @@ class MappingBridgeGeneratorImpl(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -120,10 +120,11 @@ class SimpleBridgeGeneratorImpl(
|
|||||||
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
|
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
|
||||||
}
|
}
|
||||||
KotlinPlatform.NATIVE -> {
|
KotlinPlatform.NATIVE -> {
|
||||||
|
val externCPrefix = if (libraryForCStubs.language == Language.CPP) "extern \"C\" " else ""
|
||||||
val functionName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
|
val functionName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
|
||||||
if (independent) kotlinLines.add("@" + topLevelKotlinScope.reference(KotlinTypes.independent))
|
if (independent) kotlinLines.add("@" + topLevelKotlinScope.reference(KotlinTypes.independent))
|
||||||
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
|
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
|
||||||
"$cReturnType $functionName ($joinedCParameters)"
|
"$externCPrefix$cReturnType $functionName ($joinedCParameters)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nativeLines.add(cFunctionHeader + " {")
|
nativeLines.add(cFunctionHeader + " {")
|
||||||
|
|||||||
+1
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.native.interop.indexer.*
|
|||||||
fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
|
fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
|
||||||
StructDef.Kind.STRUCT -> tryRenderStruct(def)
|
StructDef.Kind.STRUCT -> tryRenderStruct(def)
|
||||||
StructDef.Kind.UNION -> tryRenderUnion(def)
|
StructDef.Kind.UNION -> tryRenderUnion(def)
|
||||||
|
StructDef.Kind.CLASS -> tryRenderStruct(def)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryRenderStruct(def: StructDef): String? {
|
private fun tryRenderStruct(def: StructDef): String? {
|
||||||
|
|||||||
+16
-2
@@ -63,7 +63,7 @@ class StubIrBridgeBuilder(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val mappingBridgeGenerator: MappingBridgeGenerator =
|
private val mappingBridgeGenerator: MappingBridgeGenerator =
|
||||||
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
|
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, context.libraryForCStubs.language)
|
||||||
|
|
||||||
private val propertyAccessorBridgeBodies = mutableMapOf<PropertyAccessor, String>()
|
private val propertyAccessorBridgeBodies = mutableMapOf<PropertyAccessor, String>()
|
||||||
private val functionBridgeBodies = mutableMapOf<FunctionStub, List<String>>()
|
private val functionBridgeBodies = mutableMapOf<FunctionStub, List<String>>()
|
||||||
@@ -260,6 +260,9 @@ class StubIrBridgeBuilder(
|
|||||||
isVararg = isVararg or parameter.isVararg
|
isVararg = isVararg or parameter.isVararg
|
||||||
val parameterName = parameter.name.asSimpleName()
|
val parameterName = parameter.name.asSimpleName()
|
||||||
val bridgeArgument = when {
|
val bridgeArgument = when {
|
||||||
|
function.isCxxInstanceMember() && index == 0 -> {
|
||||||
|
"rawPtr"
|
||||||
|
}
|
||||||
parameter in builderResult.bridgeGenerationComponents.cStringParameters -> {
|
parameter in builderResult.bridgeGenerationComponents.cStringParameters -> {
|
||||||
bodyGenerator.pushMemScoped()
|
bodyGenerator.pushMemScoped()
|
||||||
"$parameterName?.cstr?.getPointer(memScope)"
|
"$parameterName?.cstr?.getPointer(memScope)"
|
||||||
@@ -289,7 +292,18 @@ class StubIrBridgeBuilder(
|
|||||||
bridgeArguments,
|
bridgeArguments,
|
||||||
independent = false
|
independent = false
|
||||||
) { nativeValues ->
|
) { 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)
|
bodyGenerator.returnResult(result)
|
||||||
functionBridgeBodies[function] = bodyGenerator.build()
|
functionBridgeBodies[function] = bodyGenerator.build()
|
||||||
|
|||||||
+4
-1
@@ -28,12 +28,15 @@ class StubIrContext(
|
|||||||
if (platform == KotlinPlatform.JVM) {
|
if (platform == KotlinPlatform.JVM) {
|
||||||
add("jni.h")
|
add("jni.h")
|
||||||
}
|
}
|
||||||
|
if (configuration.library.language == Language.CPP) {
|
||||||
|
add("new")
|
||||||
|
}
|
||||||
addAll(configuration.library.includes)
|
addAll(configuration.library.includes)
|
||||||
},
|
},
|
||||||
compilerArgs = configuration.library.compilerArgs,
|
compilerArgs = configuration.library.compilerArgs,
|
||||||
additionalPreambleLines = configuration.library.additionalPreambleLines +
|
additionalPreambleLines = configuration.library.additionalPreambleLines +
|
||||||
when (configuration.library.language) {
|
when (configuration.library.language) {
|
||||||
Language.C -> emptyList()
|
Language.C, Language.CPP -> emptyList()
|
||||||
Language.OBJECTIVE_C -> listOf("void objc_terminate();")
|
Language.OBJECTIVE_C -> listOf("void objc_terminate();")
|
||||||
}
|
}
|
||||||
).precompileHeaders()
|
).precompileHeaders()
|
||||||
|
|||||||
+40
-14
@@ -80,6 +80,17 @@ internal class StructStubBuilder(
|
|||||||
}
|
}
|
||||||
val classifier = context.getKotlinClassForPointed(decl)
|
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 ->
|
val fields: List<PropertyStub?> = def.fields.map { field ->
|
||||||
try {
|
try {
|
||||||
assert(field.name.isNotEmpty())
|
assert(field.name.isNotEmpty())
|
||||||
@@ -179,18 +190,33 @@ internal class StructStubBuilder(
|
|||||||
val annotation = AnnotationStub.CStruct.VarType(def.size, def.align).takeIf {
|
val annotation = AnnotationStub.CStruct.VarType(def.size, def.align).takeIf {
|
||||||
context.generationMode == GenerationMode.METADATA
|
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(
|
return listOf(ClassStub.Simple(
|
||||||
classifier,
|
classifier,
|
||||||
origin = origin,
|
origin = origin,
|
||||||
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
|
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
|
||||||
constructors = listOf(primaryConstructor),
|
constructors = listOf(primaryConstructor),
|
||||||
methods = emptyList(),
|
methods = methods,
|
||||||
modality = ClassStubModality.NONE,
|
modality = ClassStubModality.NONE,
|
||||||
annotations = listOfNotNull(structAnnotation),
|
annotations = listOfNotNull(structAnnotation),
|
||||||
superClassInit = superClassInit,
|
superClassInit = superClassInit,
|
||||||
@@ -628,12 +654,12 @@ internal class GlobalStubBuilder(
|
|||||||
val getter = when (context.platform) {
|
val getter = when (context.platform) {
|
||||||
KotlinPlatform.JVM -> {
|
KotlinPlatform.JVM -> {
|
||||||
PropertyAccessor.Getter.SimpleGetter().also {
|
PropertyAccessor.Getter.SimpleGetter().also {
|
||||||
val extra = BridgeGenerationInfo(global.name, mirror.info)
|
val extra = BridgeGenerationInfo(global.fullName, mirror.info)
|
||||||
context.bridgeComponentsBuilder.arrayGetterBridgeInfo[it] = extra
|
context.bridgeComponentsBuilder.arrayGetterBridgeInfo[it] = extra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KotlinPlatform.NATIVE -> {
|
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 {
|
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
|
||||||
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
|
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
|
||||||
}
|
}
|
||||||
@@ -647,12 +673,12 @@ internal class GlobalStubBuilder(
|
|||||||
val getter = when (context.platform) {
|
val getter = when (context.platform) {
|
||||||
KotlinPlatform.JVM -> {
|
KotlinPlatform.JVM -> {
|
||||||
PropertyAccessor.Getter.SimpleGetter().also {
|
PropertyAccessor.Getter.SimpleGetter().also {
|
||||||
val getterExtra = BridgeGenerationInfo(global.name, mirror.info)
|
val getterExtra = BridgeGenerationInfo(global.fullName, mirror.info)
|
||||||
context.bridgeComponentsBuilder.getterToBridgeInfo[it] = getterExtra
|
context.bridgeComponentsBuilder.getterToBridgeInfo[it] = getterExtra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KotlinPlatform.NATIVE -> {
|
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 {
|
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
|
||||||
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
|
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
|
||||||
}
|
}
|
||||||
@@ -664,12 +690,12 @@ internal class GlobalStubBuilder(
|
|||||||
val setter = when (context.platform) {
|
val setter = when (context.platform) {
|
||||||
KotlinPlatform.JVM -> {
|
KotlinPlatform.JVM -> {
|
||||||
PropertyAccessor.Setter.SimpleSetter().also {
|
PropertyAccessor.Setter.SimpleSetter().also {
|
||||||
val setterExtra = BridgeGenerationInfo(global.name, mirror.info)
|
val setterExtra = BridgeGenerationInfo(global.fullName, mirror.info)
|
||||||
context.bridgeComponentsBuilder.setterToBridgeInfo[it] = setterExtra
|
context.bridgeComponentsBuilder.setterToBridgeInfo[it] = setterExtra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KotlinPlatform.NATIVE -> {
|
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 {
|
PropertyAccessor.Setter.ExternalSetter(listOf(cCallAnnotation)).also {
|
||||||
context.wrapperComponentsBuilder.setterToWrapperInfo[it] = WrapperGenerationInfo(global)
|
context.wrapperComponentsBuilder.setterToWrapperInfo[it] = WrapperGenerationInfo(global)
|
||||||
}
|
}
|
||||||
@@ -682,10 +708,10 @@ internal class GlobalStubBuilder(
|
|||||||
kotlinType = mirror.pointedType
|
kotlinType = mirror.pointedType
|
||||||
val getter = when (context.generationMode) {
|
val getter = when (context.generationMode) {
|
||||||
GenerationMode.SOURCE_CODE -> {
|
GenerationMode.SOURCE_CODE -> {
|
||||||
PropertyAccessor.Getter.InterpretPointed(global.name, kotlinType.toStubIrType())
|
PropertyAccessor.Getter.InterpretPointed(global.fullName, kotlinType.toStubIrType())
|
||||||
}
|
}
|
||||||
GenerationMode.METADATA -> {
|
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 {
|
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
|
||||||
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global, passViaPointer = true)
|
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global, passViaPointer = true)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-1
@@ -12,6 +12,17 @@ private val StubOrigin.ObjCMethod.isOptional: Boolean
|
|||||||
fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod &&
|
fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod &&
|
||||||
this.origin.isOptional
|
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
|
val StubContainer.isInterface: Boolean
|
||||||
get() = if (this is ClassStub.Simple) {
|
get() = if (this is ClassStub.Simple) {
|
||||||
modality == ClassStubModality.INTERFACE
|
modality == ClassStubModality.INTERFACE
|
||||||
@@ -97,4 +108,4 @@ val StubType.underlyingTypeFqName: String
|
|||||||
is AbbreviatedType -> underlyingType.underlyingTypeFqName
|
is AbbreviatedType -> underlyingType.underlyingTypeFqName
|
||||||
is FunctionalType -> classifier.fqName
|
is FunctionalType -> classifier.fqName
|
||||||
is TypeParameterType -> name
|
is TypeParameterType -> name
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -184,7 +184,8 @@ class StubIrTextEmitter(
|
|||||||
if (element in bridgeBuilderResult.excludedStubs) return
|
if (element in bridgeBuilderResult.excludedStubs) return
|
||||||
|
|
||||||
val header = run {
|
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 receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
|
||||||
val typeParameters = renderTypeParameters(element.typeParameters)
|
val typeParameters = renderTypeParameters(element.typeParameters)
|
||||||
val override = if (element.isOverride) "override " else ""
|
val override = if (element.isOverride) "override " else ""
|
||||||
@@ -660,4 +661,4 @@ class StubIrTextEmitter(
|
|||||||
"$name : ${renderStubType(it)}"
|
"$name : ${renderStubType(it)}"
|
||||||
} ?: name
|
} ?: name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-8
@@ -121,16 +121,20 @@ private fun Properties.putAndRunOnReplace(key: Any, newValue: Any, beforeReplace
|
|||||||
this[key] = newValue
|
this[key] = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
|
private fun selectNativeLanguage(config: DefFile.DefFileConfig, hintIsCPP: Boolean = false): Language {
|
||||||
val languages = mapOf(
|
val languages = mapOf(
|
||||||
"C" to Language.C,
|
"C" to Language.C,
|
||||||
|
"C++" to Language.CPP,
|
||||||
"Objective-C" to Language.OBJECTIVE_C
|
"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 =
|
private fun parseImports(dependencies: List<KotlinLibrary>): ImportsImpl =
|
||||||
@@ -220,8 +224,6 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
|
|||||||
cinteropArguments.linkerOptions.value.toTypedArray()
|
cinteropArguments.linkerOptions.value.toTypedArray()
|
||||||
val verbose = cinteropArguments.verbose
|
val verbose = cinteropArguments.verbose
|
||||||
|
|
||||||
val language = selectNativeLanguage(def.config)
|
|
||||||
|
|
||||||
val entryPoint = def.config.entryPoints.atMostOne()
|
val entryPoint = def.config.entryPoints.atMostOne()
|
||||||
val linkerOpts =
|
val linkerOpts =
|
||||||
def.config.linkerOpts.toTypedArray() +
|
def.config.linkerOpts.toTypedArray() +
|
||||||
@@ -294,7 +296,7 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
|
|||||||
|
|
||||||
|
|
||||||
File(nativeLibsDir).mkdirs()
|
File(nativeLibsDir).mkdirs()
|
||||||
val outCFile = tempFiles.create(libName, ".${language.sourceFileExtension}")
|
val outCFile = tempFiles.create(libName, ".${library.language.sourceFileExtension}")
|
||||||
|
|
||||||
val logger = if (verbose) {
|
val logger = if (verbose) {
|
||||||
{ message: String -> println(message) }
|
{ message: String -> println(message) }
|
||||||
@@ -451,6 +453,14 @@ internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
|
|||||||
return tool
|
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(
|
internal fun buildNativeLibrary(
|
||||||
tool: ToolConfig,
|
tool: ToolConfig,
|
||||||
def: DefFile,
|
def: DefFile,
|
||||||
@@ -462,7 +472,8 @@ internal fun buildNativeLibrary(
|
|||||||
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
|
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
|
||||||
|
|
||||||
val headerFiles = def.config.headers + additionalHeaders
|
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 {
|
val compilerOpts: List<String> = mutableListOf<String>().apply {
|
||||||
addAll(def.config.compilerOpts)
|
addAll(def.config.compilerOpts)
|
||||||
addAll(tool.defaultCompilerOpts)
|
addAll(tool.defaultCompilerOpts)
|
||||||
@@ -476,6 +487,7 @@ internal fun buildNativeLibrary(
|
|||||||
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
|
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
|
||||||
addAll(when (language) {
|
addAll(when (language) {
|
||||||
Language.C -> emptyList()
|
Language.C -> emptyList()
|
||||||
|
Language.CPP -> if (cppOptions) emptyList() else listOf("-x", "c++")
|
||||||
Language.OBJECTIVE_C -> {
|
Language.OBJECTIVE_C -> {
|
||||||
// "Objective-C" within interop means "Objective-C with ARC":
|
// "Objective-C" within interop means "Objective-C with ARC":
|
||||||
listOf("-fobjc-arc")
|
listOf("-fobjc-arc")
|
||||||
|
|||||||
@@ -3823,6 +3823,14 @@ createInterop("leakMemoryWithRunningThread") {
|
|||||||
it.extraOpts "-Xcompile-source", "$projectDir/interop/leakMemoryWithRunningThread/leakMemory.cpp"
|
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)) {
|
if (PlatformInfo.isAppleTarget(project)) {
|
||||||
createInterop("objcSmoke") {
|
createInterop("objcSmoke") {
|
||||||
it.defFile 'interop/objc/objcSmoke.def'
|
it.defFile 'interop/objc/objcSmoke.def'
|
||||||
@@ -4182,6 +4190,18 @@ task interop_sourceCodeStruct(type: KonanLocalTest) {
|
|||||||
source = "codegen/intrinsics/interop_sourceCodeStruct.kt"
|
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
|
TODO: This test isn't run automatically
|
||||||
task interop_echo_server(type: RunInteropKonanTest) {
|
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())
|
||||||
|
}
|
||||||
@@ -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 компилятор и так сделает копию.
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
*/
|
||||||
@@ -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"
|
||||||
@@ -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++
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user