Rb++ / RbGCCXML

Ruby extensions have never been so easy

Project Links

Comments / Questions, Bug Reports, Patches, etc should all be submitted via the Rubyforge Project page.

All downloads and news posts will be available there as well.

The source code for all related rb++ projects are hosted on GitHub

rb++

Synopsis

Rb++ is a code generation system using RbGCCXML and Rice to make the creation of Ruby extensions of C++ libraries as easy as possible.

Rb++ is released under the MIT Licence

Documentation

View the project’s README and RDocs here.

Installation

  gem install rbplusplus

Using rb++

To give an idea of how easy it is to wrap C++ libraries into Ruby extensions, we’ll go through the wrapping of the libnoise library, a library built to easily generate coherent noise.

This libnoise wrapper is a part of the under-development Ogre.rb library. The following code will only run under the Ogre.rb environment so feel free to check out the project and follow along.

We’ll start by showing the full source code of the wrapper, which will then be broken down by each important part.

First, the code

 1 require 'rbplusplus'
 2 require 'fileutils'
 3 include RbPlusPlus
 4 
 5 OGRE_RB_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
 6 NOISE_DIR = File.join(OGRE_RB_ROOT, "tmp", "noise")
 7 HERE_DIR = File.join(OGRE_RB_ROOT, "wrappers", "noise")
 8 
 9 Extension.new "noise" do |e|
10   e.working_dir = File.join(OGRE_RB_ROOT, "generated", "noise")
11   e.sources [
12       File.join(NOISE_DIR, "include/noise.h"),
13       File.join(HERE_DIR, "code", "noiseutils.h")
14     ],
15     :library_paths => File.join(OGRE_RB_ROOT, "lib", "noise"),
16     :include_paths => File.join(OGRE_RB_ROOT, "tmp", "noise", "include"),
17     :libraries => "noise",
18     :include_source_files => [
19       File.join(HERE_DIR, "code", "noiseutils.cpp"), 
20       File.join(HERE_DIR, "code", "noiseutils.h"),
21       File.join(HERE_DIR, "code", "custom_to_from_ruby.cpp"), 
22       File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")
23     ],
24     :includes => File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")
25 
26   e.module "Noise" do |m|
27     m.namespace "noise"
28 
29     m.module "Model" do |model|
30       model.namespace "model"
31     end
32 
33     m.module "Utils" do |utils|
34       node = utils.namespace "utils"
35       node.classes("NoiseMapBuilder").methods("SetCallback").ignore
36 
37       # Ignore all but the default constructors
38       node.classes("NoiseMap").constructors.find(:arguments => [nil, nil]).ignore
39       node.classes("NoiseMap").constructors.find(:arguments => [nil]).ignore
40 
41       node.classes("Image").constructors.find(:arguments => [nil, nil]).ignore
42       node.classes("Image").constructors.find(:arguments => [nil]).ignore
43     end
44 
45     m.module "Module" do |mod|
46       node = mod.namespace "module"
47 
48       # Ignore pure virtual
49       node.classes("Module").methods("GetSourceModuleCount").ignore
50       node.classes("Module").methods("GetValue").ignore
51     end
52   end
53 end


At first glance, this is probably quite overwhelming, but it breaks down into two very easy-to-understand sections: souce code / compilation setup and wrapping definitions.

Source Code / Compliation Setup

 1 Extension.new "noise" do |e|
 2   e.working_dir = File.join(OGRE_RB_ROOT, "generated", "noise")
 3   e.sources [
 4       File.join(NOISE_DIR, "include/noise.h"),
 5       File.join(HERE_DIR, "code", "noiseutils.h")
 6     ],
 7     :library_paths => File.join(OGRE_RB_ROOT, "lib", "noise"),
 8     :include_paths => File.join(OGRE_RB_ROOT, "tmp", "noise", "include"),
 9     :libraries => "noise",
10     :include_source_files => [
11       File.join(HERE_DIR, "code", "noiseutils.cpp"), 
12       File.join(HERE_DIR, "code", "noiseutils.h"),
13       File.join(HERE_DIR, "code", "custom_to_from_ruby.cpp"), 
14       File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")
15     ],
16     :includes => File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")
17 


This section of the code constitutes the Setup portion of the wrapper. We’ll go through each line to understand what all is happening here.


1 Extension.new "noise" do |e|


All extensions start with this declaration. The string passed in will be the final name of the extension. In this case, this will generate an extension named “noise.so”.


1   e.working_dir = File.join(OGRE_RB_ROOT, "generated", "noise")


The Working Directory is the place where all generated code is placed. By default, the Working Directory is “generated”, local to the ruby script. In the case that this is not sufficient, such as this wrapper, use this attribute to set another directory.

Please note that all directories used in the script should be full paths to prevent problems, though this is not required.


 1   e.sources [
 2       File.join(NOISE_DIR, "include/noise.h"),
 3       File.join(HERE_DIR, "code", "noiseutils.h")
 4     ],
 5     :library_paths => File.join(OGRE_RB_ROOT, "lib", "noise"),
 6     :include_paths => File.join(OGRE_RB_ROOT, "tmp", "noise", "include"),
 7     :libraries => "noise",
 8     :include_source_files => [
 9       File.join(HERE_DIR, "code", "noiseutils.cpp"), 
10       File.join(HERE_DIR, "code", "noiseutils.h"),
11       File.join(HERE_DIR, "code", "custom_to_from_ruby.cpp"), 
12       File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")
13     ],
14     :includes => File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")


This is the bulk of the required setup for this wrapper to function properly. Extension#sources is the method that controls configuration of what source code is to be wrapped, where such code is to be found, any extra code to be added to the extension, and how compilation works (flags, etc). We’ll go through each part on it’s own.


1   e.sources [
2       File.join(NOISE_DIR, "include/noise.h"),
3       File.join(HERE_DIR, "code", "noiseutils.h")
4     ],


The first argument to Extension#sources is the only one required. It is where you specify what source header files are to be parsed for function / class / method definitions to wrap into an extension. Here, because libnoise gives us a “noise.h” file that itself includes the rest of the headers, we only need to specify this one file. Noiseutils is a seperate, downloadable set of utility methods and classes that we also want to put in our extension, so we specify its header here as well.

Note For any and all places where file paths are expected, said paths can be in one of these forms:


1     :library_paths => File.join(OGRE_RB_ROOT, "lib", "noise"),
2     :include_paths => File.join(OGRE_RB_ROOT, "tmp", "noise", "include"),
3     :libraries => "noise",


These three options map directly onto compiler flags.

If you are getting errors about GCCXML being unable to find certain header files, :include_paths helps with fixing this as well.


1     :include_source_files => [
2       File.join(HERE_DIR, "code", "noiseutils.cpp"), 
3       File.join(HERE_DIR, "code", "noiseutils.h"),
4       File.join(HERE_DIR, "code", "custom_to_from_ruby.cpp"), 
5       File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")
6     ],


This option is simply allowing you to specify extra C++ source files that need to be copied into the working directory and compiled in with the extension, but not necessarily parsed and wrapped.

“custom_to_from_ruby.{c,h}pp” here is currently how you would handle any requirement of writing C++ wrapper code that rb++ doesn’t handle automatically. The contents of these files are on github:

In this case, there is some duplication between :include_source_files, the first argument of Extension#sources, and (further along in this tutorial) :includes, which should be minimized in future versions of rb++. For now, the duplication is unfortunately required for this wrapper to compile and run properly.


1     :includes => File.join(HERE_DIR, "code", "custom_to_from_ruby.hpp")


The final option given to Extension#sources fits in with :include_source_files above. When there’s a header file that needs to be included in all of the generated source files (such as custom_to_from_ruby.hpp), it must be specified with this option. As said before, this will most likely made simplier and cleaner in the next release of rb++.

With this, rb++ is fully configured to create an extension from C++ source code. However, because C++ features and standards and Ruby features and standards don’t often match up, rb++ makes available many tools for manipulating the resulting extension to fit the Ruby-way.

Wrapping Definitions

 1   e.module "Noise" do |m|
 2     m.namespace "noise"
 3 
 4     m.module "Model" do |model|
 5       model.namespace "model"
 6     end
 7 
 8     m.module "Utils" do |utils|
 9       node = utils.namespace "utils"
10       node.classes("NoiseMapBuilder").methods("SetCallback").ignore
11 
12       # Ignore all but the default constructors
13       node.classes("NoiseMap").constructors.find(:arguments => [nil, nil]).ignore
14       node.classes("NoiseMap").constructors.find(:arguments => [nil]).ignore
15 
16       node.classes("Image").constructors.find(:arguments => [nil, nil]).ignore
17       node.classes("Image").constructors.find(:arguments => [nil]).ignore
18     end
19 
20     m.module "Module" do |mod|
21       node = mod.namespace "module"
22 
23       # Ignore pure virtual
24       node.classes("Module").methods("GetSourceModuleCount").ignore
25       node.classes("Module").methods("GetValue").ignore
26     end
27   end
28 end


The rest of the code handles defining the final Ruby extension. rb++’s API is meant to be as simple and obvious as possible, though there are some nuances that need to be explained.


1   e.module "Noise" do |m|
2     m.namespace "noise"
3 


Extensions can have any number of Modules defined in them. This defines a “Noise” module as a top-level module in the extension.

The #namespace call seen here is the hook into the rb++ code querying and processing system. rb++ works primarily on C++ namespaces; code must be contained in a namespace to be wrapped into a Ruby extension. The main reason for this is to block out any system-level code that might get included (such as STL), and secondly to help organize the code.

This call is then specifying that all code in the “noise” namespace should be wrapped under the Noise module. Note that this is not recursive. Deeper namespaces must be manually specified, as you’ll see below.


1     m.module "Model" do |model|
2       model.namespace "model"
3     end
4 


Here we’re wrapping code in the C++ “noise::model” namespace into Noise::Module in Ruby. Modules can be nested infinitely deep.


 1     m.module "Utils" do |utils|
 2       node = utils.namespace "utils"
 3       node.classes("NoiseMapBuilder").methods("SetCallback").ignore
 4 
 5       # Ignore all but the default constructors
 6       node.classes("NoiseMap").constructors.find(:arguments => [nil, nil]).ignore
 7       node.classes("NoiseMap").constructors.find(:arguments => [nil]).ignore
 8 
 9       node.classes("Image").constructors.find(:arguments => [nil, nil]).ignore
10       node.classes("Image").constructors.find(:arguments => [nil]).ignore
11     end


As was mentioned at the beginning, many times C++ does not adhere to Ruby, either in style, layout, or in Rice’s ability to handle functionality. This section of the code shows off the internal querying system available via RbGCCXML and added on top of that the ability to specify which parts of C++ do or do not actually get wrapped.


1       node = utils.namespace "utils"


To gain access to the underlying C++ node tree and RbGCCXML’s querying system, simply save the return value of the #namespace call. This node will be the RbGCCMXL Namespace node for that C++ namespace.


1       node.classes("NoiseMapBuilder").methods("SetCallback").ignore


Here we see both the power of the RbGCCXML query system and rb++’s ability to mark nodes for exclusion. For this method, NoiseMapBuilder::SetCallback, we need to ignore it from the wrapping because Rice does not currently know how to handle methods with function pointer arguments.


1       # Ignore all but the default constructors
2       node.classes("NoiseMap").constructors.find(:arguments => [nil, nil]).ignore
3       node.classes("NoiseMap").constructors.find(:arguments => [nil]).ignore
4 
5       node.classes("Image").constructors.find(:arguments => [nil, nil]).ignore
6       node.classes("Image").constructors.find(:arguments => [nil]).ignore


These lines are here because Rice currently does not handle method overloads, including in constructors, though it will not complain if multiple constructors are wrapped. Because of this, it’s difficult to know which constructor will actually be available in Ruby, so we ignore all of those we know we don’t want, leaving just one.


 1     m.module "Module" do |mod|
 2       node = mod.namespace "module"
 3 
 4       # Ignore pure virtual
 5       node.classes("Module").methods("GetSourceModuleCount").ignore
 6       node.classes("Module").methods("GetValue").ignore
 7     end
 8   end
 9 end


This finishes up the wrapper definition, specifying one more Module to create and a few methods to ignore (again overloaded methods but in this case we ignore all of them).

And that’s the entire wrapper. Simply run this file and after a few minutes, a new Ruby extension will appear in working_dir/. To follow compilation progress, tail the file working_dir/rbpp_compile.log.

For this specific extension, if you have Ogre.rb checked out, run the following commands from ogrerb/ to build the wrapper:

The resulting library will be found in lib/, and you can now run the samples found in noise/samples.

Back to Top

© 2008 Jason Roelofs | Generated by webgen | Design by Andreas Viklund.