Building your Ruby environment and accessing it.
You can create classes both from C and Ruby together. Define the class in Ruby code just as a regular Ruby class, and then add C-code the manipulate the class definition. Or define everything in C. Your choice.
Lets first define some class in Ruby code, and try to access this from C:
Create a file wiki-example.rb
with the following content. This class is already prepared for the next section (manipulating Ruby classes from C). We now only need the 'get_version' method.
module WikiExample
class WikiManager
attr_accessor :active
def connect
self.active = _we_connected
end
def get_version
return 2
end
# _we_connected() is defined in C
end
end
Then we write a C stub and small program to access this code. We first initialize mruby, then load the code file and the third step is to access the module, class and instance method.
Name the file wiki-example.c
.
#include "mruby.h"
#include "mruby/irep.h"
int
main(void)
{
mrb_state *mrb = mrb_open();
if (!mrb) { /* handle error */ }
FILE *fp = fopen("wiki-example.rb","r");
// Load the data from the .rb file into the Ruby environment
mrb_value obj = mrb_load_file(mrb,fp);
// close the file
fclose(fp);
// First access the module
struct RClass *module = mrb_module_get(mrb, "WikiExample");
// Get the class that is defined in the WikiExample module
struct RClass *class = mrb_class_get_under(mrb, module, "WikiManager");
// Create a new instance of WikiManager, no arguments are needed (0, NULL)
mrb_value c = mrb_obj_new(mrb, class, 0, NULL);
// Call the get_version method on the instance.
mrb_value res = mrb_funcall(mrb, c, "get_version", 0);
// Convert the result (a fixed number wrapped in a mrb_value)
printf("result: %i\n", mrb_fixnum(res));
// If crashed, provide exception info
if (mrb->exc)
{
mrb_print_error(mrb);
}
// Close the Ruby environment
mrb_close(mrb);
}
Compile this code with gcc -std=c99 -Iinclude wiki-example.c build/host/lib/libmruby.a -o wiki-example
and then run wiki-example
. The output is result: 2
.
The strategy is first to update the existing class. Here we add a C-method to the class, but we can even define the module and class from C.
To add a method to a class, we first need a reference to the class. We can copy the lines from the first example for getting a reference to the class WikiManager, of just insert the next lines just before the If crashed...
comment line.
// add the method to the WikiManager class
mrb_define_method(mrb, class, "_we_connected", we_connected, MRB_ARGS_NONE());
// call the connect method on WikiManager
mrb_value res2 = mrb_funcall(mrb, c, "connect", 0);
Then we add the following method between the #include
and int main
lines:
mrb_value
we_connected(mrb_state* mrb, mrb_value self){
// we need an int from the args
mrb_int i = 0;
// retrieve one arg of type int (see mruby.h)
mrb_get_args(mrb, "i", &i);
// we always need to return a mrb_value object, and mrb_bool_value converts a C bool to a mrb_value.
return mrb_bool_value(i >= 2);
}
Compile (see above) and run. You will get the output result: 2
. Now edit the Ruby file and change the get_version to return 1. Do not compile, just run the executable again, and see the output result: 1 Connection failed
.
This will give you unlimited results, as you don't even need to recompile to update your Ruby code. Image writing unit-tests in Ruby calling your C-methods in classes. You will be able to mock (semi)classes the Ruby-way.
Passing the arguments is quite straightforward (both ways). But maybe someone can update this page with some code on how to pass a C struct to Ruby and use the result.