rb++ / rbgccxml

Ruby extensions have never been so easy

Project Links

Comments / Questions, Bug Reports, Patches, etc should all be submitted via the projects' Github pages.

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 (also called noise.rb) 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

At first glance, this is probably quite overwhelming, but it breaks down into two very easy-to-understand sections: "source code / compilation setup":#source and "wrapping definitions":#definitions. h4. Source Code / Compliation Setup

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.

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".

The Working Directory is the place where all generated code is placed. By default, the Working Directory is called “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 this script uses full directory paths. This is not required, but recommended to prevent confusion or conflicts.

This block of code sets up the extension's source information. 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.

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:

  • Straight string, like “/path/to/file.h”
  • Glob, such as “/where/headers/are/*.h”
  • An array of strings [“/here/header1.h”, “/there/header2.h”, …]
  • An array of globs [“/usr/lib/library/*.h”, “/usr/lib/other_lib/*.hpp”]
These three options map directly onto compiler flags. * :library_paths are added as -L * :include_paths are added as -I * :libraries are added as -l Also available for direct command line manipulation: * :ldflags * :cxxflags

The option :include_source_dir tells rb++ to take all source files it finds in the given directory and use them during the compilation step. Any source files it finds (.c / .cpp) get copied into working_dir and compiled into the extension. Any header file it finds (.h / .hpp) will get included into all generated source files and compiled into the extension. This option is a combination of two more explicit options:

  • :include_source_files
  • :includes

This option :include_source_files allows you to specify extra C++ source files that need to be copied into the working directory and compiled in with the extension.

The other option, :includes, is used when you need header file(s) that needs to be included in all of the generated source files but not parsed by 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 often don’t match up, rb++ makes available many tools for manipulating the code to fit the Ruby-way.

Wrapping Definitions

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.

Extensions can have any number of Modules defined in them. This defines a “Noise” module as a top-level module in the extension in which all further wrapping will take place.

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 an 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.

Here we're wrapping code in the noise::model namespace into the Noise::Model module in Ruby. Modules can be nested infinitely deep.

As was mentioned at the beginning, depending on the design of the library, most code written in C++ does not adhere to The Ruby Way, and in some cases simply cannot be wrapped into Ruby (say, void* arguments or return values). This section of the wrapper tells rb++ how to handle these cases, and allows manipulation of the parsed code, providing complete control over the layout of the resulting extension.

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 "noise::utils".

As Rice does not currently support method overloading, it only properly wraps one constructor. The code here shows two different ways of specifying exactly which constructor should be exposed. For the class NoiseMap, we only want to expose the default constructor. There are three different constructors on this class, so we ignore the constructors we don’t want.

For documentation purposes, selecting the appropriate constructor for the Image class is done via #use_constructor, which takes the constructor node that should be exposed into Ruby.

These methods aren't properly wrapping in Rice due to there being multiple overloads to this method, so for now we just ignore them all.

This finishes up the wrapper definition, specifying one more Module to create which wraps the code in the “noise::module” namespace.

This also shows how to hook into Rice’s Director system. If you are faimilar with SWIG’s %director, this is very much the same. For any class that should polymorphically call code from C++ into Ruby, it needs to be given the #director directive. Future versions of this library will be able to automatically find these classes and build all directors as needed.

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:

  • rake noise:setup
  • rake noise:build

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

Back to Top

© 2010 Jason Roelofs | Generated by jekyll | Design by Andreas Viklund.