C++ Bindings
ruby-bindgen creates Ruby bindings for C++ libraries using Rice. Creating C++ bindings takes more work than creating C bindings, so if a library provides both a C and C++ API you should use the C API.
ruby-bindgen does its best to generate compilable Rice code. It has been battle tested against OpenCV, which is a large, complex C++ API with over a thousand classes and ten thousand methods.
For many libraries, the generated bindings will compile and work with no additional changes. For example, Rice includes a fully automated example binding for the BitmapPlusPlus library.
For more complex libraries, like OpenCV, some customization will likely be required.
Configuration
Below is an example rice-bindings.yaml configuration file:
project: my_extension
input: ./include
output: ./ext/generated
format: Rice
match:
- "**/*.hpp"
clang:
args:
- -I./include
- -std=c++17
- -xc++
To generate bindings run:
ruby-bindgen rice-bindings.yaml
Output
Header Files
For every C++ header file, ruby-bindgen generates a .hpp and .cpp file. These files have the same name as the C++ header but with the addition of -rb at the end. For example, a C++ header file called matrix.hpp will generate in matrix-rb.hpp and matrix-rb.cpp. If the C++ header file declares a template class, then a third file will be generated called matrix-rb.ipp.
flowchart LR
subgraph Input
H1["matrix.hpp"]
CF["rice-bindings.yaml"]
end
H1 & CF --> RB["ruby-bindgen"]
subgraph "Rice Output"
R1["matrix-rb.cpp"]
R2["matrix-rb.hpp"]
R3["matrix-rb.ipp"]
end
RB --> R1 & R2 & R3
The .hpp file declares an Init_ function (e.g., Init_Matrix) that registers the classes, methods, and enums from that header with Rice.
The .cpp file defines that Init_ function with the actual Rice bindings code.
The .ipp file is only generated when the header declares template classes. It contains one _instantiate function per template class that can be used to instantiate the template. The -rb.ipp files can be reused across translated units. See Templates for details.
Init Function Names
Each generated file has an Init_ function. To avoid conflicts when multiple files have the same name in different directories (e.g., core/version.hpp and dnn/version.hpp), the function name includes the directory path:
| File Path | Init Function |
|---|---|
version.hpp |
Init_Version |
core/version.hpp |
Init_Core_Version |
dnn/version.hpp |
Init_Dnn_Version |
core/hal/interface.hpp |
Init_Core_Hal_Interface |
The top-level directory is always removed to avoid overly long names (e.g., opencv2/calib3d.hpp becomes Init_Calib3d, and opencv2/core/version.hpp becomes Init_Core_Version).
Project Files
When you set the project option in your configuration, ruby-bindgen also generates project-level wrapper files that tie everything together:
flowchart LR
subgraph Input
CF["rice-bindings.yaml"]
end
CF --> RB["ruby-bindgen"]
subgraph "Project Files"
P1["my_extension-rb.cpp"]
P2["my_extension-rb.hpp"]
P3["rice_include.hpp"]
end
RB --> P1 & P2 & P3
my_extension-rb.hppdeclares the main Ruby init functionInit_MyExtensionwith the appropriate export attribute (__declspec(dllexport)on Windows,__attribute__((visibility("default")))on Linux/macOS)my_extension-rb.cppdefinesInit_MyExtension, which calls each per-headerInit_function (e.g.,Init_Matrix,Init_Image,Init_Filter)rice_include.hppis the default include header that all generated translation units include
To suppress project file generation, omit the project option from your configuration. This is useful when you want to manage the top-level init function yourself.
Include Header
By default ruby-bindgen creates a default include files called rice_include.hpp. Its content is:
#pragma once
// Default Rice include header generated by ruby-bindgen
// To customize, create your own header and specify it with the 'include:' config option
#include <rice/rice.hpp>
#include <rice/stl.hpp>
rice_include.hpp already exists, ruby-bindgen will not overwrite it.
All generated headers include this file:
#include "../../rice_include.hpp" // relative path computed automatically
void Init_MyClass();
Remember that C++ templates are instantiated per translation unit. If different translation units see different template definitions (e.g., one sees a Type<T> specialization and another doesn't), this causes an ODR (One Definition Rule) violation. The linker may silently pick the wrong instantiation, leading to subtle bugs.
By centralizing all Rice includes in a single header that every generated file includes, all translation units see the same template definitions, preventing ODR violations.
The include header is an ideal candidate for precompiled headers (PCH). Since every generated file includes it, precompiling this header can significantly speed up build times:
# CMake example
target_precompile_headers(my_extension PRIVATE
"${CMAKE_SOURCE_DIR}/rice_include.hpp"
)
You can specify a custom include file by setting the include option in the configuration file. This will replace the default rice_include.hpp file and thus allow you to specify your own include files if necessary. Your custom header must include the Rice headers, plus any addition includes or definitions you want to be global.
For example, it is a good place to add support for custom smart pointers. For example, OpenCV defines cv::Ptr<T> which inherits from std::smart_pointer<T>. The opencv-ruby bindings wrapp this smart pointer in a custom header file that is then added to rice_includes.hpp. That means all translation units see it, preventing ODR violations.
A custom include file will not be overwritten by ruby-bindgen.
Init Function Call Graph
When Ruby loads an extension, it calls the top-level Init_<extension> function, which in turn calls each per-header Init_* function to register classes, methods, and enums.
flowchart TD
A["Ruby loads extension"] --> B["Init_MyExtension"]
B --> C["Init_Core"]
B --> D["Init_Mat"]
B --> E["Init_*"]
B --> G["Init_*_Refinements (manual, optional)"]
The extension Init_<extension> function is the top-level entry point and calls all per-header Init_* functions. Refinement init functions are optional manual hooks that run after generated definitions. See refinements for details.
Build System
After generating Rice bindings, you will need to setup a build system for your extension. ruby-bindgen can generate CMake build files to compile and link the generated bindings.
Example
For a complete, fully automated example see BitmapPlusPlus-ruby.