Customizing Bindings
ruby-bindgen tries 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.
However, complex libraries may require some customization. Customizations fall into three categories:
- Configuration
- Refinements
- Fixes
Configuration
Some issues are best solved via the configuration file rather than editing generated code:
- Symbol filtering: Skip functions that cause linker errors or aren’t meant for external use by name or regex pattern
- Version guards: Wrap symbols in
#if VERSION >= Nguards so bindings compile against multiple library versions - Export macros: Limit bindings to exported symbols, preventing linker errors from internal functions
- Name mappings: Override generated Ruby class and method names with exact strings or regex patterns
Refinements
Ruby classes are open, meaning you can reopen an existing class and add methods to it. Refinements take advantage of this to extend generated bindings with additional functionality. Note this is not quite the same as Ruby's refinements functionality since which are scoped — these additions are global.
Common reasons to add refinements include:
| Addition | What It Adds |
|---|---|
| Expected methods | inspect, to_s |
| Modules | include_module(rb_mComparable), define_method("<=>") |
| Template instantiations | Mat<unsigned char> |
| Type conversions | to_* |
| Exceptions | Custom exception class hierarchy |
| Custom type handling | Type<T>, From_Ruby<T>, and To_Ruby<T> |
| Custom STL names | define_vector<cv::Vec3b>(...) |
| Non-member operators | +, -, *, /, == |
| Iteration | Add std::iterator_types |
| Method renames | rb_alias to rename methods to match Ruby idioms |
Just like in Ruby, you can take advantage of Ruby’s open classes to add new functionality.
To do this, create a directory to contain additions. The directory can be named anything, but a suggested convention is to name it refinements. Here is an example layout:
ext/
├── myextension # Generated files
│ ├── matrix-rb.hpp
│ ├── matrix-rb.hpp
│ └── ...
├── refinements/ # Manual additions (never overwritten)
│ ├── CMakeLists.txt
│ ├── matrix-rb.hpp # to_s, template instantiations
│ ├── matrix-rb.cpp # to_s, template_instantiations
│ └── ...
└── myextension-rb.cpp # Main init file, calls Init_*_Refinements()
Create a new file in refinements for each generated Rice file you want to override. For example, if you want to add functionality to matrix-rb.cpp, then create refinements/matrix-rb.hpp and refinements/matrix-rb.cpp. As part of those files, define a new init function called Init_Matrix_Refinements.
Next add the .cpp file to refinements/CMakeLists.txt:
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
"matrix-rb.cpp")
Init_MyExtension function in the project my_extension-rb.cpp file, include the header and call the Init_Matrix_Refinements method.
Using refinements has a lot of advantages:
- Regeneration Safe: Your updates will not be overwritten with the exception of the
my_extension-rb.cppfile - Reuse
_instantiatemethods - Refinements can#includegenerated.ippfiles to call_instantiatefunctions with new type arguments.
Example
Here is an example refinements/matrix-rb.cpp refinements file:
#include <matrix>
#include "matrix-rb.hpp" // Generated header
#include "../ext/matrix-rb.ipp" // Generated _instantiate functions
using namespace Rice;
void Init_Matrix_Refinements()
{
// Reopen the class that was already defined by generated code
Rice::Data_Type<Matrix> matrix;
matrix.
define_method("to_s", [](const Matrix& self) -> std::string
{
std::ostringstream stream;
stream << self;
return stream.str();
});
// Rename a method to better match Ruby idioms
rb_alias(matrix, rb_intern("[]"), rb_intern("call"));
// Instantiate additional templates not in the generated code
Matrix_instantiate<double>(rb_mMyExtensin, "MatrixDouble");
}
Fixes
Sometimes you need to modify the generated Rice code. Common reasons include:
| Change Type | Example | Why |
|---|---|---|
| Missing includes | #include <core.hpp> |
Generated file references types from headers it doesn't include |
| Version guards | #if VERSION >= 2 |
API only available in certain library versions |
| Default values | Remove Stream::Null() default |
Avoid hardware-dependent functions such as CUDA initialization |
- Mark manual changes with
// Manualcomments so they're searchable:grep -r "// Manual" ext/ - Include order matters: System/library headers should come before the primary header, local project headers after
- Watch fluent chain syntax when commenting out methods: change the preceding
.to;to terminate the chain - Refinements can include generated
.ippfiles to reuse_instantiatefunctions with additional type arguments
If you have manual edits, you'll will need a strategy for preserving them when you regenerate bindings.