Skip to content

"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.


Last update: December 1, 2022