Object type preservation¶
Having an inheritance tree makes it possible to keep a subtype object as a base type object. If we construct base type object in the target bindings, we will lose the information about object's actual type. To deal with this problem, CppBind does object type preservation when constructing an object of the user-defined type. After that, saved type information is used to recover the original object in C bindings and construct the object in target language bindings with the correct type.
To implement this feature, CppBind keeps track of the descendant classes/structs for each class/struct. We pass the object's actual type information from C bindings to target language binding, where the object construction process is done. CppBind allows the user to define the list of descendants via the descendants variable for each class, otherwise using the deduced one. In the target language bindings, the object construction with the actual type is done with switch-case blocks where we compare preserved type information with the possible descendant subtypes.
Note
Currently, CppBind has some limitations when calculating the list of descendants in case of class templates: it is not able to separate descendants per instantiation of the class template. That's why there are redundant descendants in the list, which leads to the generation of unreachable if/else branches.
Here is an example of a base class with a user-defined list of descendant classes:
/**
* An example interface which descendant types are templates. Used for an example of downcast in target language.
* __API__
* action: gen_interface
* package: templates
*/
class Container {
public:
virtual ~Container() = default;
};
Implementation part of above-described process in target language bindings (the main logic is inside "cppbindConstructObject" method)
/**
* ______ .______ .______ .______ __ .__ __. _______
* / || _ \ | _ \ | _ \ | | | \ | | | \
* | ,----'| |_) | | |_) | | |_) | | | | \| | | .--. |
* | | | ___/ | ___/ | _ < | | | . ` | | | | |
* | `----.| | | | | |_) | | | | |\ | | '--' |
* \______|| _| | _| |______/ |__| |__| \__| |_______/
*
* This file is generated by cppbind on 11/22/2022-10:16.
* Please do not change it manually.
*/
package com.examples.templates
import com.examples.cppbind.*
import com.examples.cppbind.exceptions.*
import com.examples.getters.NumberInt
import com.examples.simple.Project
import com.examples.simple.Root
import com.examples.simple.task.Task
import com.examples.templates.stack.MyStackRoot
import com.examples.templates.stack.RootsStack
/**
* An example interface which descendant types are templates. Used for an example of downcast in target language.
*/
interface IContainer : AutoCloseable {
/**
* An internal getter to get the id of an object.
* It is intended to be used by the generated code.
*/
val cppbindObjId: Long
/**
* An internal method to bind the lifetimes of the current and another object.
* It is intended to be used by the generated code.
*/
fun keepCppBindReference(ref: Any)
companion object {
/**
* An internal method to create a Kotlin object from a C++ object.
* It is intended to be used by the generated code.
*/
public fun cppbindConstructObject(id: Long, owner: Boolean = false): IContainer {
val idType = jGettypebyid(id)
val obj : Any
when (idType) {
MyStackProject.cppbindCxxTypeName -> obj = MyStackProject(CppBindObject(id, owner))
MyStackTask.cppbindCxxTypeName -> obj = MyStackTask(CppBindObject(id, owner))
MyStackRoot.cppbindCxxTypeName -> obj = MyStackRoot(CppBindObject(id, owner))
StackPrj.cppbindCxxTypeName -> obj = StackPrj(CppBindObject(id, owner))
StackTask.cppbindCxxTypeName -> obj = StackTask(CppBindObject(id, owner))
StackNumInt.cppbindCxxTypeName -> obj = StackNumInt(CppBindObject(id, owner))
RootsStack.cppbindCxxTypeName -> obj = RootsStack(CppBindObject(id, owner))
else -> obj = ContainerImpl(CppBindObject(id, owner))
}
return obj as IContainer
}
}
}
open class ContainerImpl
internal constructor(obj : CppBindObject) : IContainer {
companion object {
init {
System.loadLibrary("wrapper_jni")
}
/**
* An internal property to keep an information about the underlying C++ object type.
* It is intended to be used by the generated code.
*/
const val cppbindCxxTypeName: String = "cppbind::example::Container"
}
protected var cppbindObj = obj
private var refs: MutableList<Any> = mutableListOf()
override fun keepCppBindReference(ref: Any) {
refs.add(ref)
}
override val cppbindObjId: Long
get() {
if (cppbindObj.id == 0L) {
throw RuntimeException("Object is not allocated")
}
return cppbindObj.id
}
/**
* An internal property returning underlying C++ object id.
* It is intended to be used by the generated code.
*/
internal val cxxId: Long by lazy {
jGetcxxid(cppbindObj.id)
}
/**
* An internal property returning underlying C++ type name.
* It is intended to be used by the generated code.
*/
internal val cxxTypeName: String by lazy {
jGettypebyid(cppbindObj.id)
}
override fun close() {
if (cppbindObj.owner && cppbindObj.id != 0L) {
jFinalize(cppbindObj.id)
cppbindObj.id = 0L
}
}
/**
* Finalize and deletes the object
*/
protected fun finalize() {
close()
}
/**
* CppBind generated hashCode method returning the hash of underlying C++ object id.
*/
override fun hashCode(): Int {
return cxxId.hashCode()
}
/**
* CppBind generated equals method comparing the underlying C++ object ids.
*/
override fun equals(other: Any?): Boolean {
other as ContainerImpl
return cxxId == other.cxxId
}
/**
* CppBind generated toString method returning underlying C++ object type and id.
*/
override fun toString(): String {
return "<0x$cxxId: $cxxTypeName>"
}
///// External wrapper functions ////////////
private external fun jFinalize(id: Long): Unit
private external fun jGetcxxid(id: Long): Long
}
/**
* An example of usage for a type which descendants are template types.
* Used for an example of downcast in target language.
*/
open class ContainerHolder
internal constructor(obj: CppBindObject) : AutoCloseable {
companion object {
init {
System.loadLibrary("wrapper_jni")
}
protected fun constructHelper(container: IContainer): Long {
val kotlintojdkcontainer = container.cppbindObjId
val id = jConstructor(kotlintojdkcontainer, container)
return id
}
@JvmStatic
private external fun jConstructor(container: Long, vararg extraObjs: Any?): Long
/**
* An internal property to keep an information about the underlying C++ object type.
* It is intended to be used by the generated code.
*/
const val cppbindCxxTypeName: String = "cppbind::example::ContainerHolder"
}
protected var cppbindObj = obj
private var refs: MutableList<Any> = mutableListOf()
/**
* An internal method to bind the lifetimes of the current and another object.
* It is intended to be used by the generated code.
*/
fun keepCppBindReference(ref: Any) {
refs.add(ref)
}
/**
* An internal getter to get the id of an object.
* It is intended to be used by the generated code.
*/
open val cppbindObjId: Long
get() {
if (cppbindObj.id == 0L) {
throw RuntimeException("Object is not allocated")
}
return cppbindObj.id
}
/**
* An internal property returning underlying C++ object id.
* It is intended to be used by the generated code.
*/
internal val cxxId: Long by lazy {
jGetcxxid(cppbindObj.id)
}
/**
* An internal property returning underlying C++ type name.
* It is intended to be used by the generated code.
*/
internal val cxxTypeName: String by lazy {
jGettypebyid(cppbindObj.id)
}
constructor(container: IContainer): this(CppBindObject(constructHelper(container), true)) {
}
val container: IContainer
get() {
val result = jContainer(cppbindObjId)
val jdktokotlinresult : IContainer
jdktokotlinresult = IContainer.cppbindConstructObject(result)
jdktokotlinresult.keepCppBindReference(this)
return jdktokotlinresult
}
/**
* CppBind generated hashCode method returning the hash of underlying C++ object id.
*/
override fun hashCode(): Int {
return cxxId.hashCode()
}
/**
* CppBind generated equals method comparing the underlying C++ object ids.
*/
override fun equals(other: Any?): Boolean {
other as ContainerHolder
return cxxId == other.cxxId
}
/**
* CppBind generated toString method returning underlying C++ object type and id.
*/
override fun toString(): String {
return "<0x$cxxId: $cxxTypeName>"
}
override fun close() {
if (cppbindObj.owner && cppbindObj.id != 0L) {
jFinalize(cppbindObj.id)
cppbindObj.id = 0L
}
}
/**
* Finalize and deletes the object
*/
protected fun finalize() {
close()
}
///// External wrapper functions ////////////
private external fun jContainer(id: Long): Long
private external fun jFinalize(id: Long): Unit
private external fun jGetcxxid(id: Long): Long
}
private external fun jGettypebyid(id: Long): String
Note
Object type preservation process for Python is done by pybind.
/**
* ______ .______ .______ .______ __ .__ __. _______
* / || _ \ | _ \ | _ \ | | | \ | | | \
* | ,----'| |_) | | |_) | | |_) | | | | \| | | .--. |
* | | | ___/ | ___/ | _ < | | | . ` | | | | |
* | `----.| | | | | |_) | | | | |\ | | '--' |
* \______|| _| | _| |______/ |__| |__| \__| |_______/
*
* This file is generated by cppbind on 10/24/2022-14:00.
* Please do not change it manually.
*/
import CWrapper
import Foundation
/// An example interface which descendant types are templates. Used for an example of downcast in target language.
public protocol Container {
/// An internal property to keep a reference to the original C++ object.
/// It is intended to be used by the generated code.
var cself: CppBindCObject { get }
/// An internal method to bind the lifetimes of the current and another object.
/// It is intended to be used by the generated code.
func keepCppBindReference(_ object: Any)
}
extension Container {
}
public class ContainerImpl: Container {
public let cself: CppBindCObject
public let owner: Bool
private var refs: [Any]
/// internal main initializer
internal required init(_ _cself: CppBindCObject, _ _owner: Bool = false) {
self.cself = _cself
self.owner = _owner
self.refs = []
}
deinit {
release_CppbindExample_ContainerImpl(cself, owner)
}
public func keepCppBindReference(_ object: Any) {
self.refs.append(object)
}
/// An internal property to keep an information about the underlying C++ object type.
/// It is intended to be used by the generated code.
class var cppbindCxxTypeName : String { return "cppbind::example::Container" }
/// An internal method to create a Swift object from a C++ object.
/// It is intended to be used by the generated code.
class func cppbindConstructObject(_ cppbindObj: CppBindCObject, _ owner: Bool = false) -> Container {
let typeName = String(cString: cppbindObj.type)
var obj : Any
switch(typeName) {
case(MyStackProject.cppbindCxxTypeName):
obj = MyStackProject(cppbindObj, owner)
case(MyStackTask.cppbindCxxTypeName):
obj = MyStackTask(cppbindObj, owner)
case(MyStackRoot.cppbindCxxTypeName):
obj = MyStackRoot(cppbindObj, owner)
case(StackPrj.cppbindCxxTypeName):
obj = StackPrj(cppbindObj, owner)
case(StackTask.cppbindCxxTypeName):
obj = StackTask(cppbindObj, owner)
case(StackNumInt.cppbindCxxTypeName):
obj = StackNumInt(cppbindObj, owner)
case(RootsStack.cppbindCxxTypeName):
obj = RootsStack(cppbindObj, owner)
default:
obj = ContainerImpl(cppbindObj, owner)
}
return obj as! Container
}
}
/// An example of usage for a type which descendants are template types.
/// Used for an example of downcast in target language.
public class ContainerHolder {
/// An internal property to keep a reference to the original C++ object.
/// It is intended to be used by the generated code.
public let cself: CppBindCObject
/// An internal property to keep track whether Swift is responsible for deallocating the underlying C++ object or not.
/// It is intended to be used by the generated code.
public let owner: Bool
private var refs: [Any]
/// internal main initializer
internal required init(_ _cself: CppBindCObject, _ _owner: Bool = false) {
self.cself = _cself
self.owner = _owner
self.refs = []
}
deinit {
release_CppbindExample_ContainerHolder(cself, owner)
}
/// An internal method to bind the lifetimes of the current and another object.
/// It is intended to be used by the generated code.
public func keepCppBindReference(_ object: Any) {
self.refs.append(object)
}
public convenience init(container: Container) {
let swifttosccontainer = container.cself
var cppbindErr = CppBindCObject()
self.init(create_CppbindExample_ContainerHolder(swifttosccontainer, &cppbindErr), true)
if cppbindErr.type != nil {
let errorType = String(cString: cppbindErr.type!)
switch errorType {
case ("std::exception"):
let excObj = StdException(cppbindErr, true)
ExceptionHandler.handleUncaughtException(excObj.what())
default:
cppbindErr.type.deallocate()
ExceptionHandler.handleUncaughtException("Uncaught Exception")
}
}
}
public var container: Container {
var cppbindErr = CppBindCObject()
let result = _prop_get_CppbindExample_ContainerHolder_container(cself, &cppbindErr)
if cppbindErr.type != nil {
let errorType = String(cString: cppbindErr.type!)
switch errorType {
case ("std::exception"):
let excObj = StdException(cppbindErr, true)
ExceptionHandler.handleUncaughtException(excObj.what())
default:
cppbindErr.type.deallocate()
ExceptionHandler.handleUncaughtException("Uncaught Exception")
}
}
var sctoswiftresult: Container
sctoswiftresult = ContainerImpl.cppbindConstructObject(result)
sctoswiftresult.keepCppBindReference(self)
return sctoswiftresult
}
/// An internal property to keep an information about the underlying C++ object type.
/// It is intended to be used by the generated code.
class var cppbindCxxTypeName : String { return "cppbind::example::ContainerHolder" }
}