“Hello user” example¶
It is time to introduce the most basic usage of CppBind. Let’s assume you wrote a fantastic 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.
Okay, here is your 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 other languages, you need to create a binding from your C++ library to that language. CppBind is a tool that helps you achieve that by adding some extra marks to your C++ codes.
In the code above, you need to export the UserInfo structure with age and name properties and the Host class with its methods.
To do that, you need to change your Doxygen comments to include CppBind instructions and parameters.
.. _Basic example C++ with CppBind:
#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?");
}
};
We have just added the __API__ tag to let CppBind know what entity needs to be processed and added the instruction action: gen_method, which tells that a method needs to be generated. For the complete list of available instructions, see Generation instructions.
You can notice the usage of the throws variable in API comments. CppBind has required variables on some entities. In this case, we have set a mandatory throws variable on all methods/constructors. This requirement is done to ensure that the user hasn’t forgotten to mention possible exceptions that the entity can throw. More details can be found here. That is it. You should be able to use it on your codes written in supported languages. Here are usage examples for Kotlin, Python, and Swift.
package hello_user_usage
import com.hello_user.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 hello_user.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 Wrapper
@main
class HelloUser {
static func main() {
let user = UserInfo(userName: "John", userAge: 22)
let young_user = UserInfo(userName: "Kate", userAge: 18)
let 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?")
}
}
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.
CppBind setup¶
To run CppBind, you need to have a config file for your project. The config file should include rules and definitions of minimal required parameters. CppBind provides a command-line utility to generate a default config file for your project. Run cppbind init in the project root directory to create the default project config file.
The content of the default config file provided by CppBind
vars:
out_prj_dir: "."
src_glob:
- ./**/*.h*
src_exclude_glob: []
extra_headers:
- stdexcept
- new
- typeinfo
package_prefix: ""
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')}}"
python.pybind_module: ""
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
The above-provided default config file can be changed later to fit the requirements of your project. For example, the default value the src_glob variable is a list with the file glob pattern corresponding to all the header files in the project. This can be modified to control the list of the files processed by CppBind. Some other variables (cxx_out_dir, out_dir, etc.) need to be adjusted to control the place where the generated bindings will be placed. You can find the whole list of CppBind variables here.
Note
From the default config file, you can notice the usage of the get_android_ndk_sysroot helper function when defining the clang_args variable for Kotlin target language on the macOS platform. ANDROID_NDK environment variable must be correctly set since it is used by get_android_ndk_sysroot function.
The content of "Hello user" project config file
type_vars:
!join
- !include std_exc_api.yaml
dir_vars:
- dir: "."
vars:
project_dir: '{{_current_working_dir}}'
project_link: 'https://github.com/PicsArt/cppbind/tree/master/examples/tutorials/hello_user'
vars: |
{%- set out_prj_dir = "." -%}
out_prj_dir: {{out_prj_dir}}
src_glob:
- cxx/**/*.h*
include_dirs:
- .
extra_headers:
- stdexcept
- new
- typeinfo
mac.clang_args:
- --sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
file_postfix: ""
kotlin.cxx_out_dir: {{path.join(out_prj_dir, "kotlin/wrappers")}}
kotlin.out_dir: {{path.join(out_prj_dir, "kotlin/src/main/java")}}
kotlin.package_prefix: com.hello_user
kotlin.include_dirs:
- ./
- /usr/lib/jvm/java-11-openjdk-amd64/include
python.cxx_out_dir: {{path.join(out_prj_dir, "python/wrappers")}}
python.out_dir: {{path.join(out_prj_dir, "python/src")}}
python.package_prefix: hello_user
python.pybind_module: hello_user
swift.package_prefix: hello_user
swift.cxx_out_dir: {{path.join(out_prj_dir, "swift/wrappers")}}
swift.out_dir: {{path.join(out_prj_dir, "swift/src")}}
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
After generating bindings, you should include them with C++ source codes in your project build. In our tutorial example, we use Bazel to build the project.
Bazel setups and rules for the “Hello user” example can be found here.