"Array" tutorial¶
"Hello user" tutorial shows how to annotate source files to tell CppBind what to expose in target languages. Let's examine the case of using a third-party library. In this case it's impossible to modify source files, so you can't just write CppBind API annotations inside class/function doxygen style comments. CppBind offers another mechanism for writing annotations in this scenario: it's possible to write annotations in separate config yaml files.
Let's consider we have an Array
class which is declared in a
third-party library:
#include <vector>
namespace cppbind {
class Array {
public:
Array() {}
int front() const {
return _elements.front();
}
int back() const {
return _elements.back();
}
void push_back(int element) {
_elements.push_back(element);
}
int size() const {
return _elements.size();
}
bool empty() const {
return _elements.empty();
}
int& operator[] (int i) {
return _elements[i];
}
int at(int i) const {
return _elements.at(i);
}
private:
std::vector<int> _elements;
};
}
In order to generate bindings, as a first step you need to configure
your project. You can use the default configuration provided by CppBind
with running cppbind init
command in your project root directory.
The content of default config file provided by CppBind
vars:
out_prj_dir: "."
src_glob:
- ./**/*.h*
extra_headers:
- stdexcept
- new
- typeinfo
include_dirs:
- .
kotlin.clang_args:
- -D__ANDROID__
mac.kotlin.target_arch: x86_64
mac.kotlin.clang_args:
- -D__ANDROID__
- --target={{target_arch}}-none-linux-android
- --sysroot={{get_android_ndk_sysroot(getenv('ANDROID_NDK'))}}
mac.python.clang_args:
- --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
mac.swift.clang_args:
- --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
kotlin.cxx_out_dir: "{{path.join(out_prj_dir, 'kotlin/wrappers')}}"
kotlin.out_dir: "{{path.join(out_prj_dir, 'kotlin/src/main/java')}}"
python.cxx_out_dir: "{{path.join(out_prj_dir, 'python/wrappers')}}"
python.out_dir: "{{path.join(out_prj_dir, 'python/src')}}"
swift.cxx_out_dir: "{{path.join(out_prj_dir, 'swift/wrappers')}}"
swift.out_dir: "{{path.join(out_prj_dir, 'swift/src')}}"
type_vars:
!join
- !include std_exc_api.yaml
var_def:
!join
- !include variable_definitions.yaml
rules:
kotlin.code_snippets:
!join
- !include kotlin/code_snippets.yaml
python.code_snippets:
!join
- !include python/code_snippets.yaml
swift.code_snippets:
!join
- !include swift/code_snippets.yaml
kotlin.type_converters:
!join
- !include "kotlin/*_types.yaml"
python.type_converters:
!join
- !include "python/*_types.yaml"
swift.type_converters:
!join
- !include "swift/*_types.yaml"
kotlin.actions:
!join
- !include kotlin/actions.yaml
python.actions:
!join
- !include python/actions.yaml
swift.actions:
!join
- !include swift/actions.yaml
Above provided default config file can be changed later to fit the
requirements of your project. For example, we can consider the change of
the src_glob variable. It defines the list of the files processed by
CppBind. src_glob default value corresponds to all the header files
in the project. For this tutorial's case, we have changed the value of
src_glob variable in the project config file, since our C++ source
file is located under cxx directory: src_glob: ["cxx/array.cpp"]
.
More information about CppBind variables can be
here.
Note
When generating bindings on the macOS platform for Kotlin, you need the ANDROID_NDK environment variable to be set. It should point to the directory where android NDK is installed. CppBind requires ANDROID_NDK environment variable to populate the correct clang arguments.
After project configuration is done, you need to annotate your source code with CppBind API annotations. Third-party library source code annotation should need to be done separately, in a new yaml config file:
type_vars:
- type: "cppbind::Array"
vars:
action: gen_class
package: array
- type: "cppbind::Array::Array()"
vars:
action: gen_constructor
throws: no_throw
- type: "cppbind::Array::front()"
vars:
action: gen_method
throws: no_throw
- type: "cppbind::Array::back()"
vars:
action: gen_method
throws: no_throw
- type: "cppbind::Array::push_back(int)"
vars:
action: gen_method
throws: no_throw
- type: "cppbind::Array::size()"
vars:
action: gen_getter
throws: no_throw
- type: "cppbind::Array::empty()"
vars:
action: gen_getter
throws: no_throw
- type: "cppbind::Array::operator[](int)"
vars:
action: gen_method
throws: no_throw
- type: "cppbind::Array::at(int)"
vars:
action: gen_method
throws:
- std::out_of_range
In the above-mentioned yaml config file you can find API instructions
for Array
class. API instructions are associated with the appropriate
entity with the help of C++ signature which are written in front of the
type key. Writing instructions in config file has the same effect as
writing them in doxygen style comments. More detailed information about
this mechanism can be found here: Section type_vars.
To make written API annotations available for CppBind we use project config files merging feature: type_vars sections of project config files can be spread all over the project directories and merged together by CppBind. By default, CppBind searches files with cppbind.yaml suffix under project directories and merges type_vars sections defined in those files. In our tutorial, we have written API annotations for the "Array" class in the file named array.cppbind.yaml. To change the name pattern of the project config files, you should modify context_def_glob application variable (more details can be found here. Another way to include API annotations written in a separate file is including the file in the project config file, under type_vars section (the same way as it's done for std_exc_api.yaml file in default config file).
After project configuration is done and annotation config file is
written, you should run CppBind command line to generate bindings:
cppbind koltin swift python
.
Generated bindings
/**
* ______ .______ .______ .______ __ .__ __. _______
* / || _ \ | _ \ | _ \ | | | \ | | | \
* | ,----'| |_) | | |_) | | |_) | | | | \| | | .--. |
* | | | ___/ | ___/ | _ < | | | . ` | | | | |
* | `----.| | | | | |_) | | | | |\ | | '--' |
* \______|| _| | _| |______/ |__| |__| \__| |_______/
*
* This file is generated by cppbind on 08/18/2022-11:29.
* Please do not change it manually.
*/
package example_lib.array
import example_lib.cppbind.*
import example_lib.cppbind.exceptions.*
open class Array
internal constructor(obj: CppBindObject) : AutoCloseable {
companion object {
init {
System.loadLibrary("wrapper_jni")
}
protected fun constructHelper(): Long {
val id = jConstructor()
return id
}
@JvmStatic
private external fun jConstructor(): 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::Array"
}
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(): this(CppBindObject(constructHelper(), true)) {
}
val size: Int
get() {
val result = jSize(cppbindObjId)
return result
}
val empty: Boolean
get() {
val result = jEmpty(cppbindObjId)
return result
}
fun front(): Int {
val result = jFront(cppbindObjId)
return result
}
fun back(): Int {
val result = jBack(cppbindObjId)
return result
}
fun push_back(element: Int): Unit {
val result = jPush_back(cppbindObjId, element)
return result
}
operator fun get(i: Int): Int {
val result = jGet(cppbindObjId, i)
return result
}
operator fun set(i: Int, value: Int){
jSet(cppbindObjId, i, value)
}
fun at(i: Int): Int {
val result = jAt(cppbindObjId, i)
return result
}
/**
* 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 Array
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 jFront(id: Long): Int
private external fun jBack(id: Long): Int
private external fun jPush_back(id: Long, element: Int, vararg extraObjs: Any?): Unit
private external fun jSize(id: Long): Int
private external fun jEmpty(id: Long): Boolean
private external fun jGet(id: Long, i: Int, vararg extraObjs: Any?): Int
private external fun jSet(id: Long, i: Int, value: Int, vararg extraObjs: Any?)
private external fun jAt(id: Long, i: Int, vararg extraObjs: Any?): Int
private external fun jFinalize(id: Long): Unit
private external fun jGetcxxid(id: Long): Long
}
private external fun jGettypebyid(id: Long): String
"""
______ .______ .______ .______ __ .__ __. _______
/ || _ \ | _ \ | _ \ | | | \ | | | \
| ,----'| |_) | | |_) | | |_) | | | | \| | | .--. |
| | | ___/ | ___/ | _ < | | | . ` | | | | |
| `----.| | | | | |_) | | | | |\ | | '--' |
\______|| _| | _| |______/ |__| |__| \__| |_______/
This file is generated by cppbind on 06/09/2022-12:37.
Please do not change it manually.
"""
from __future__ import annotations
from typing import *
import pybind_example_lib.array.array as pybind_array
from example_lib.cppbind.cppbind_metaclass import *
from example_lib.cppbind.cppbind_utils import *
class Array(metaclass=CppBindMetaclass):
@bind
def __init__(self):
pass
@property
@bind
def size(self) -> int:
pass
@property
@bind
def empty(self) -> bool:
pass
@bind
def front(self) -> int:
pass
@bind
def back(self) -> int:
pass
@bind
def push_back(self, element: int) -> None:
pass
@bind
def __getitem__(self, i: int) -> int:
pass
@bind
def __setitem__(self, i: int, value: int):
pass
@bind
def at(self, i: int) -> int:
pass
@bind
def __repr__(self) -> str:
"""
CppBind generated __repr__ method returning underlying C++ object type and id.
"""
pass
/**
* ______ .______ .______ .______ __ .__ __. _______
* / || _ \ | _ \ | _ \ | | | \ | | | \
* | ,----'| |_) | | |_) | | |_) | | | | \| | | .--. |
* | | | ___/ | ___/ | _ < | | | . ` | | | | |
* | `----.| | | | | |_) | | | | |\ | | '--' |
* \______|| _| | _| |______/ |__| |__| \__| |_______/
*
* This file is generated by cppbind on 08/14/2022-11:16.
* Please do not change it manually.
*/
import CWrapper
import Foundation
public class Array {
/// 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_Cppbind_Array(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() {
var cppbindErr = CppBindCObject()
self.init(create_Cppbind_Array(&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 size: Int {
var cppbindErr = CppBindCObject()
let result = _prop_get_Cppbind_Array_size(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")
}
}
let sctoswiftresult = Int(result)
return sctoswiftresult
}
public var empty: Bool {
var cppbindErr = CppBindCObject()
let result = _prop_get_Cppbind_Array_empty(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")
}
}
return result
}
public func front() -> Int {
var cppbindErr = CppBindCObject()
let result = _func_Cppbind_Array_front(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")
}
}
let sctoswiftresult = Int(result)
return sctoswiftresult
}
public func back() -> Int {
var cppbindErr = CppBindCObject()
let result = _func_Cppbind_Array_back(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")
}
}
let sctoswiftresult = Int(result)
return sctoswiftresult
}
public func push_back(element: Int) -> Void {
let swifttoscelement = CInt(element)
var cppbindErr = CppBindCObject()
_func_Cppbind_Array_push_back(cself, swifttoscelement, &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")
}
}
}
public subscript(i: Int) -> Int {
get {
let swifttosci = CInt(i)
let result = _func_Cppbind_Array__getitem_(cself, swifttosci)
let sctoswiftresult = Int(result)
return sctoswiftresult
}
set(value) {
let swifttosci = CInt(i)
let swifttoscvalue = CInt(value)
_func_Cppbind_Array__setitem_(cself, swifttosci, swifttoscvalue)
}
}
public func at(i: Int) throws -> Int {
let swifttosci = CInt(i)
var cppbindErr = CppBindCObject()
let result = _func_Cppbind_Array_at(cself, swifttosci, &cppbindErr)
if cppbindErr.type != nil {
let errorType = String(cString: cppbindErr.type!)
switch errorType {
case ("std::out_of_range"):
throw StdOutOfRange(cppbindErr, true)
case ("std::exception"):
let excObj = StdException(cppbindErr, true)
ExceptionHandler.handleUncaughtException(excObj.what())
default:
cppbindErr.type.deallocate()
ExceptionHandler.handleUncaughtException("Uncaught Exception")
}
}
let sctoswiftresult = Int(result)
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::Array" }
}
Finally, after generating bindings, you should include them along with C++ source codes in your project build. In our tutorial we use bazel to build the project. Bazel setups and rules for "Array" tutorial can be found here.
After project build setup you can write usage examples and run them:
package array_usage
import example_lib.array.*
import example_lib.cppbind.exceptions.*
class ArrayUsageApp {
companion object {
init {
System.loadLibrary("wrapper_jni")
}
@JvmStatic
fun main(args: Array<String>) {
val array = Array()
assert(array.empty)
array.push_back(0)
array.push_back(1)
array.push_back(2)
assert(!array.empty)
assert(array.size == 3)
assert(array.front() == 0)
assert(array.back() == 2)
assert(array[1] == 1)
try {
array.at(5)
assert(false)
} catch (e: StdOutOfRange) {
} catch (e: Exception) {
assert(false)
}
}
}
}
from example_lib.array.array import Array
array = Array()
assert array.empty is True
array.push_back(0)
array.push_back(1)
array.push_back(2)
assert array.empty is False
assert array.size == 3
assert array.front() == 0
assert array.back() == 2
assert array[1] == 1
try:
array.at(5)
assert False
except IndexError:
pass
except Exception:
assert False
import XCTest
import Wrapper
func runArrayTests() {
let array = Array()
assert(array.empty)
array.push_back(element: 0)
array.push_back(element: 1)
array.push_back(element: 2)
assert(!array.empty)
assert(array.size == 3)
assert(array.front() == 0)
assert(array.back() == 2)
assert(array[1] == 1)
do {
let _ = try array.at(i: 5)
assert(false)
} catch is StdOutOfRange {
} catch {
assert(false)
}
}
#if os(Linux)
runArrayTests()
#elseif os(OSX)
class ArrayTests: XCTestCase {
func testRun() throws {
runArrayTests()
}
}
#endif
So, with the help of annotations written in a separate config file, we have achieved our goal: we have exposed several types and functions from a third-party library to all supported target languages.