"Hello user" tutorial¶
Introducing the most basic usage of CppBind. Assuming you wrote a library that greets its user. And, of course, you don't want to restrict it only to C++ users. You want to make it available for other language programs as well.
Let's consider following C++ code:
#include <string>
/**
* Structure to describe user.
*/
struct UserInfo {
/**
* Creates user
*/
UserInfo(const std::string& user_name, unsigned int user_age) : age(user_age), name(user_name) {}
/**
* Age of user.
*/
unsigned int age = 0;
/**
* Name of user.
*/
std::string name;
/**
* Some wishes of the user.
*/
bool want_a_drink = false;
};
/**
* Host class.
*/
class Host {
public:
/**
* Creates host
*/
Host() = default;
/**
* Greeting function.
*/
std::string hello(const UserInfo& user) {
return (user.age > 21 ? "Hello ": "Hi ") + user.name;
}
/**
* Welcome function.
*/
std::string welcome(const UserInfo& user) {
if (!user.want_a_drink)
return "Welcome " + user.name + "! Let me know if you want something.";
return "Welcome " + user.name + "! Do you want cap of " + (user.age > 21 ? "beer?": "juice?");
}
};
To use this from another language, you need to create a binding from your C++ library to the target language. We are going to generate the bindings with CppBind.
In the code above, you need to export the UserInfo
structure with
age
and name
properties and the Host
class with its methods. 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. This will
create a cppbind.yaml file containing the following configuration:
CppBind default configuration
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. This variable defines the source files which are going to be processed by CppBind. The default value of this variable corresponds to all header files in the project. In our example we have changed the value of src_glob, since our source files are located under cxx directory. More information about CppBind variables can be found 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 add CppBind instructions and parameters in the doxygen style comments of your C++ code.
#include <string>
/**
* Structure to describe user.
* __API__
* action: gen_class
* package: hello
*/
struct UserInfo {
/**
* Creates user
* __API__
* action: gen_constructor
* throws: no_throw
*/
UserInfo(const std::string& user_name, unsigned int user_age) : age(user_age), name(user_name) {}
/**
* Age of user.
* __API__
* action: gen_property_getter
*/
unsigned int age = 0;
/**
* Name of user.
* __API__
* action: gen_property_getter
*/
std::string name;
/**
* Some wishes of the user.
* __API__
* action: gen_property_setter
*/
bool want_a_drink = false;
};
/**
* Host class.
* __API__
* action: gen_class
* package: hello
*/
class Host {
public:
/**
* Creates host
* __API__
* action: gen_constructor
* throws: no_throw
*/
Host() = default;
/**
* Greeting function.
* __API__
* action: gen_method
* throws: no_throw
*/
std::string hello(const UserInfo& user) {
return (user.age > 21 ? "Hello ": "Hi ") + user.name;
}
/**
* Welcome function.
* __API__
* action: gen_method
* throws: no_throw
*/
std::string welcome(const UserInfo& user) {
if (!user.want_a_drink)
return "Welcome " + user.name + "! Let me know if you want something.";
return "Welcome " + user.name + "! Do you want cap of " + (user.age > 21 ? "beer?": "juice?");
}
};
Here we have just added the __API__ tag to start CppBind annotation for this particular method and added the instruction action: gen_method, which tells CppBind that a binding needs to be generated for this method. For the complete list of available instructions, see Generation instructions.
You can notice the usage of the throws variable in API comments. There are variables which are required on some entities. In this case, the throws variable is required on all methods/constructors. This requirement is added to ensure that the user hasn't forgotten to mention possible exceptions that the method/constructor can throw. More details can be found here.
That is it. You should be able to use it from your codes written in target languages. Here are usage examples for Kotlin, Python, and Swift.
package hello_user_usage
import example_lib.hello.*
class HelloUserApp {
companion object {
init {
System.loadLibrary("wrapper_jni")
}
@JvmStatic
fun main(args: Array<String>) {
val user = UserInfo("John", 22)
val young_user = UserInfo("Kate", 18)
val host = Host()
assert(host.hello(user) == "Hello John")
assert(host.hello(young_user) == "Hi Kate")
assert(host.welcome(user) == "Welcome John! Let me know if you want something.")
assert(host.welcome(young_user) == "Welcome Kate! Let me know if you want something.")
user.want_a_drink = true
young_user.want_a_drink = true
assert(host.welcome(user) == "Welcome John! Do you want cap of beer?")
assert(host.welcome(young_user) == "Welcome Kate! Do you want cap of juice?")
}
}
}
from example_lib.hello.hello_user import UserInfo, Host
user = UserInfo(user_name="John", user_age=22)
young_user = UserInfo(user_name="Kate", user_age=18)
host = Host()
assert host.hello(user=user) == "Hello John"
assert host.hello(user=young_user) == "Hi Kate"
assert host.welcome(user=user) == "Welcome John! Let me know if you want something."
assert host.welcome(user=young_user) == "Welcome Kate! Let me know if you want something."
user.want_a_drink = True
young_user.want_a_drink = True
assert host.welcome(user=user) == "Welcome John! Do you want cap of beer?"
assert host.welcome(user=young_user) == "Welcome Kate! Do you want cap of juice?"
import XCTest
import Wrapper
func runHelloUserTests() {
let user = UserInfo(userName: "John", userAge: 22)
let young_user = UserInfo(userName: "Kate", userAge: 18)
let host = Wrapper.Host()
assert(host.hello(user: user) == "Hello John")
assert(host.hello(user: young_user) == "Hi Kate")
assert(host.welcome(user: user) == "Welcome John! Let me know if you want something.")
assert(host.welcome(user: young_user) == "Welcome Kate! Let me know if you want something.")
user.want_a_drink = true
young_user.want_a_drink = true
assert(host.welcome(user: user) == "Welcome John! Do you want cap of beer?")
assert(host.welcome(user: young_user) == "Welcome Kate! Do you want cap of juice?")
}
#if os(Linux)
runHelloUserTests()
#elseif os(OSX)
class HelloUserTests: XCTestCase {
func testRun() throws {
runHelloUserTests()
}
}
#endif
The above-described steps are applicable for all the new classes and functions you want to expose to other languages. Besides that, you need to configure the CppBind project file once to use the tool. To know more about the CppBind configuration, continue reading.
After generating bindings, you should include them with C++ source codes in your project build. In our tutorial, we use Bazel to build the project.
Bazel setups and rules for the "Hello user" tutorial can be found here.