Robert Mosolgo

Defining mruby Methods with C

You can use C code to prepare methods for mruby scripts.

The steps are:

  • Defining a method
  • Getting argument values
  • Adding the method to the mrb_state

Defining a Method

To define a Ruby function, make a C function that:

  • accepts two arguments, a mrb_state and a mrb_value
  • returns a mrb_value.

Here’s a minimal method definition:

mrb_value
ruby_method(mrb_state *mrb, mrb_value self)
{
  return mrb_nil_value();
}

The arguments are:

  1. mrb_state, the current mruby VM instance
  2. mrb_value, the current self (caller of this method)

Notice that you don’t define the Ruby arguments here. You’ll handle those later by getting them from the mrb_state.

The return type must be mrb_value. If it’s not, your program will crash (Segmentation Fault :( ) when the return value is accessed (no compile-time error). If your method shouldn’t return anything, use return mrb_nil_value();

mruby implements a lot of the built-in classes’ instance methods this way, for example: String#capitalize!(src).

Getting Arguments

You might have noticed that your C function definition didn’t define any arguments. Instead, you get your arguments by extracting them from mrb_state.

mrb_value
ruby_method(mrb_state *mrb, mrb_value self)
{
  // Initialize a variable
  mrb_int some_integer;
  // Extract a value
  mrb_get_args(mrb, "i", &mrb_int);

  return mrb_nil_value();
}

mrb_get_args takes a string whose letters say what kind of arguments and how many arguments to extract. The mrb_get_args source documents the different possibilities.

Notably, anything after a | is optional.

For a default value, assign a value to the variable and make the argument optional. In this example, inherit defaults to TRUE:

  mrb_bool inherit = TRUE;
  mrb_get_args(mrb, "|b", &inherit);

That’s from C implementation of Module#constants. Another nice example is the String#[] source.

Adding Methods to mruby State

To add a method to the mruby state, you must attach it to some object. To make a method global, you can define it on Object. Let’s do that.

We’ll use mrb_define_method, which accepts five arguments:

mrb_define_method(mrb_state *mrb, struct RClass *c, const char *name, mrb_func_t func, mrb_aspec aspec)
  • mrb_state *mrb: the open mruby VM instance
  • struct RClass *c: the mruby class to attach the method to
  • const char *name: the Ruby name for this method
  • mrb_func_t func: the C function to execute for this Ruby method
  • mrb_aspec aspec: the number & types of arguments for this method

In fact, specifying arguments is not currently used (github issue). To pass some value here, you can use some convenient macros from mruby.h.

So, let’s add a global method, greet!. Here’s the method:

static mrb_value
mrb_greet(mrb_state *mrb, mrb_value self) {
  printf("Hello, mruby!\n");
  return mrb_nil_value();
}

Then, attach it to Object, which will make it global:

mrb_define_method(mrb, mrb->object_class, "greet!", mrb_greet, MRB_ARGS_NONE());

Now, you can run in your Ruby script:

greet!