Tutorial#

Getting started#

Writing an extension with Rice is very similar to writing an extension with the C API.

The first step is to install the Rice gem:

gem install rice

Next create an extconf.rb file:

require 'mkmf-rice'
create_makefile('test')

Note that we use mkmf-rice instead of mkmf. This will ensure that the extension will be linked with standard C++ library and allows access to the Rice header files.

Note

For advanced users - instead of using mkmf-rice you can use your own build system such as CMake. In this case you may prefer to download the Rice header file, rice.hpp, from github and directly include it in your source tree.

Next we create our extension and save it to test.cpp:

extern "C"
void Init_test()
{
}

Note the extern "C" line above. This tells the compiler that the function Init_test should have C linkage and calling convention. This turns off name mangling so that the Ruby interpreter will be able to find the function (remember that Ruby is written in C, not C++).

So far we haven’t put anything into the extension, so it isn’t particularly useful. The next step is to define a class so we can add methods to it.

Defining Classes#

Defining a class in Rice is a single call:

#include <rice/rice.hpp>

using namespace Rice;

extern "C"
void Init_test()
{
  Class rb_cTest = define_class("Test");
}

This will create a class called Test that inherits from Object. If we wanted to inherit from a different class, we do so with the second parameter:

#include <rice/rice.hpp>

using namespace Rice;

extern "C"
void Init_test()
{
  Class rb_cMySocket = define_class("MySocket", rb_cIO);
}

Note the prefix rb_c on the name of the class. This is a convention that the Ruby interpreter and many extensions tend to use. It signifies that this is a class and not some other type of object. Some other naming conventions that are commonly used:

  • rb_c variable name prefix for a Class

  • rb_m variable name prefix for a Module

  • rb_e variable name prefix for an Exception type

  • rb_ function prefix for a function in the Ruby C API

  • rb_f_ function prefix to differentiate between an API function that takes Ruby objects as arguments and one that takes C argument types

  • rb_*_s_ indicates the function is a singleton function

  • *_m suffix to indicate the function takes variable number of arguments

Also note that we don’t include “ruby.h” directly. Rice has a wrapper for ruby.h that handles some compatibility issues across platforms and Ruby versions. Always include Rice headers before including anything that might include “ruby.h”.

Defining methods#

Now let’s add a method to our class:

#include <rice/rice.hpp>

using namespace Rice;

Object test_hello(Object /* self */)
{
  String str("hello, world");
  return str;
}

extern "C"
void Init_test()
{
  Class rb_cTest =
    define_class("Test")
    .define_method("hello", &test_hello);
}

Here we add a method Test#hello that returns the string “Hello, World”. The method takes self as an implicit parameter, but isn’t used, so we comment it out to prevent a compiler warning.

We can also add an #initialize method to our class:

#include <rice/rice.hpp>
#include <rice/stl.hpp>

using namespace Rice;

Object test_initialize(Object self)
{
  self.iv_set("@foo", 42);
}

Object test_hello(Object /* self */)
{
  String str("hello, world");
  return str;
}

extern "C"
void Init_test()
{
  Class rb_cTest =
    define_class("Test")
    .define_method("initialize", &test_initialize)
    .define_method("hello", &test_hello);
}

The initialize method sets an instance variable @foo to the value 42. The number is automatically converted to a Fixnum before doing the assignment.

Note that we’re chaining calls on the Class object. Most member functions in Module and Class return a reference to self, so we can chain as many calls as we want to define as many methods as we want.

Note

If your compiler complains about “no matching overloaded function found” followed by “could not deduce template argument for ‘Function_T” then that means you are working with an overloaded C++ function or method. As a result, you’ll need to give Rice some help as explained in the Overloaded Methods section.

Defining methods with lambdas#

It is also possible to define_methods using C++ lambdas. Similar to define_method, the lambda takes self as an implicit parameter:

Class rb_cTest =
  define_class("Test")
  .define_method("hello", [](Object& object) {
    return test_hello
  });

Note that we pass self as a reference since we do not want to copy it!

Defining functions#

It is also possible to add methods to a Ruby class using define_function. The difference is that no implicit self parameter is passed. Once again, you can use function pointers or lambdas:

void some_function()
{
  // do something
}

extern "C"
void Init_test()
{
  Class rb_cTest =
    define_class("Test")
    .define_function("some_function", &some_function);
    .define_function("some_function_lambda", []() {
      return some_function();
    });
}

Wrapping C++ Types#

It’s useful to be able to define Ruby classes in a C++ style rather than using the Ruby API directly, but the real power Rice is in wrapping already-defined C++ types.

Let’s assume we have the following C++ class that we want to wrap:

class Test
{
public:
  static std::string static_hello();
public:
  Test();
  std::string hello();
};

This is a C++ version of the Ruby class we just created in the previous section. To wrap it:

#include <rice/rice.hpp>
#include <rice/stl.hpp>

using namespace Rice;

extern "C"
void Init_test()
{
  Data_Type<Test> rb_cTest =
    define_class<Test>("Test")
    .define_constructor(Constructor<Test>())
    .define_function("static_hello", &Test::static_hello)
    .define_method("hello", &Test::hello);
}

In this example we use Data_Type<> instead of Class and the template version of define_class() instead of the non-template version. This creates a binding in the Rice library between the Ruby class Test and the C++ class Test.

Next, we define a function static_hello that is implemented by a C++ static member function. Since static functions are not tied to a specific object, there is no self parameter. Therefore we use define_function instead of define_method.

Last, we define a method hello that is implemented by a C++ member function. When Ruby calls this function, instead of passing an implicit self parameter, Rice is smart enough to direct the call to the correct C++ Test instance.

Defining attributes#

C++ structures, and sometimes classes, often have public member variables that store data. Rice makes it easy to wrap these member variables via the use of define_attr:

struct MyStruct
{
  int readOnly = 0;
  int writeOnly = 0;
  int readWrite = 0;
};

Data_Type<MyStruct> rb_cMyStrut =
  define_class<MyStruct>("MyStruct")
  .define_constructor(Constructor<MyStruct>())
  .define_attr("read_only", &MyStruct::readOnly, Rice::AttrAccess::Read)
  .define_attr("write_only", &MyStruct::writeOnly, Rice::AttrAccess::Write)
  .define_attr("read_write", &MyStruct::readWrite);
}

Notice the use of Rice::AttrAccess::Read to define read-only attributes and Rice::AttrAccess::Write for write-only attributes. If you do not specify an AttrAccess value then Rice make the attribute readable and writable.

These attributes can then be accessed in the expected way in Ruby:

my_struct = MyStruct.new
a = my_struct.read_only
my_struct.write_only = 5
my_struct.read_write = 10
b = my_struct.read_write

Similarly, you can wrap static members via the use of define_singleton_attr:

struct MyStruct
{
  static int readOnly = 0;
  static int writeOnly = 0;
  static int readWrite = 0;
};

Data_Type<MyStruct> rb_cMyStrut =
  define_class<MyStruct>("MyStruct")
  .define_constructor(Constructor<MyStruct>())
  .define_singleton_attr("read_only", &MyStruct::readOnly, Rice::AttrAccess::Read)
  .define_singleton_attr("write_only", &MyStruct::writeOnly, Rice::AttrAccess::Write)
  .define_singleton_attr("read_write", &MyStruct::readWrite);
}

These attributes can then be accessed in the expected way in Ruby:

a = MyStruct.read_only
MyStruct.write_only = 5
MyStruct.read_write = 10
b = MyStruct.read_write

Type conversions#

Rice is smart enough to convert between most Ruby and C++ objects. Let’s look again at our example class:

class Test
{
public:
  Test();
  std::string hello();
};

When we wrote our class, we never wrote a single line of code to convert the std::string returned by hello() into a Ruby type. Nevertheless, the conversion works, and when we write:

test = Test.new
puts test.hello

We get the expected result.

Rice includes default specializations for many C++ types. To define your own conversion, please refer to the Type Conversions section.

Conversions for wrapped C++ types#

Take another look at the wrapper we wrote for the Test class:

extern "C"
void Init_test()
{
  Data_Type<Test> rb_cTest =
    define_class<Test>("Test")
    .define_constructor(Constructor<Test>())
    .define_method("hello", &Test::hello);
}

When we called define_class<Test>, it created a Class for us and automatically registered the new Class with the type system, so that the calls:

Data_Object<Foo> obj(new Foo);
Foo * f = detail::From_Ruby<Foo *>::convert(obj);
Foo const * f = detail::From_Ruby<Foo const *>::convert(obj);

works as expected.

The Data_Object class is a wrapper for the TypedData_Wrap_Struct and the TypedData_Get_Struct macros in C extensions. It can be used to wrap or unwrap any class that has been assigned to a Data_Type. It inherits from Object, so any member functions we can call on an Object we can also call on a Data_Object:

Object object_id = obj.call("object_id");
std::cout << object_id << std::endl;

The Data_Object class can be used to wrap a newly-created object:

Data_Object<Foo> foo(new Foo);

or to unwrap an already-created object:

VALUE obj = ...;
Data_Object<Foo> foo(obj);

A Data_Object functions like a smart pointer:

Data_Object<Foo> foo(obj);
foo->foo();
std::cout << *foo << std::endl;

Like a VALUE or an Object, data stored in a Data_Object will be marked by the garbage collector as long as the Data_Object is on the stack.

Exceptions#

In general Rice automatically handles exceptions. For example, suppose a member function of our example class could throw an exception:

class MyException
  : public std::exception
{
};

class Test
{
public:
  Test();
  std::string hello();
  void error();
};

extern "C"
void Init_test()
{
  Data_Type<Test> rb_cTest =
    define_class<Test>("Test")
    .define_constructor(Constructor<Test>())
    .define_method("hello", &Test::hello)
    .define_method("error", &Test::error);
}

If we call this function from Ruby, C++ will raise an exception. Rice will automatically catch it and convert it to a Ruby exception:

test = Test.new
begin
  test.error()
rescue => e
  ..
end

For more information about exceptions please refer to the Exceptions section.