Skip to content

Instantly share code, notes, and snippets.

@johnbellone
Last active December 14, 2015 06:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnbellone/5044723 to your computer and use it in GitHub Desktop.
Save johnbellone/5044723 to your computer and use it in GitHub Desktop.
Defining C objects in Ruby
a.out
*.o
*dSYM

Defining C Objects in Ruby

My previous example takes some time and shows very basic Ruby virtual machine integration with a C application. This example expands on this and includes how you might wrap a C structure and integrate it with normal functionality inside of Ruby. Some examples that you'll see here include: instance and class variables, instance and class methods, using the initialize method and yielding to the calling iterator function.

Be sure to take some time and read the basic example so that you're sure to understand how it all works. The script can easily be changed in the main.c file if you want to puts some information.

You can compile this by running make. It currently assumes by default that you have the p374 version of Ruby 1.9.3 installed through RVM. If you have Ruby installed in a separate directory override the following environment variables to set the library and include paths:

$ LDFLAGS=-Lpath/to/ruby-1.9.1 -lruby-static CFLAGS=-Ipath/to/ruby-1.9.1/include make

#include <foo.h>
#include <ruby.h>
static struct Foo* get_Foo(VALUE self) {
// Use this macro to get the data structure (cast) pointer value and return
// it as it comes from the Ruby object. There may be a case when this is actually
// not adequately setup (initialized) yet.
struct Foo* object;
Data_Get_Struct(self, struct Foo, object);
return object;
}
static VALUE rb_foo_allocate(VALUE klass) {
// Create a structure of type Foo inside of the virtual machine.
// Set the default value to zero.
struct Foo* object;
return Data_Make_Struct(klass, struct Foo, rb_gc_mark, free, object);
}
static VALUE rb_foo_set_bar(VALUE self, VALUE value) {
struct Foo* object = get_Foo(self);
object->bar = NUM2INT(value);
return self;
}
static VALUE rb_foo_get_bozo(VALUE self) {
return rb_cv_get(self, "@@bozo");
}
static VALUE rb_foo_get_bar(VALUE self) {
struct Foo* object = get_Foo(self);
return INT2NUM(object->bar);
}
static VALUE rb_foo_initialize(VALUE self, VALUE opts) {
struct Foo* object = get_Foo(self);
int value = 42;
// In order to check the instance of the object we need the ID value
// in the symbol take for Hash. We do this by calling the below function
// and passing this value onward.
VALUE hklass = rb_const_get(rb_cObject, rb_intern("Hash"));
// Only execute the following bit if the opts parameter is not
// nil and is a Hash.
if (!NIL_P(opts) && rb_obj_is_instance_of(opts, hklass) == Qtrue) {
// No type checking - we're going to assume that the caller is
// being a nice citizen and not trying to mess with us.
VALUE v = rb_hash_aref(opts, rb_str_new2("bar"));
if (!NIL_P(v)) {
value = NUM2INT(v);
}
// Set the @baz instance variable if we define it in the options
// hash. Since this can be nil we're going to have to take care
// when accessing the variable.
v = rb_hash_aref(opts, rb_str_new2("baz"));
if (!NIL_P(v)) {
rb_iv_set(self, "@baz", v);
}
}
object->bar = value;
return self;
}
static VALUE rb_foo_new(VALUE klass, VALUE opts) {
// Manually call the method that we've overriden to allocate
// an instance of the Foo class. Then we'll call Foo#initialize.
VALUE self = rb_foo_allocate(klass);
rb_obj_call_init(self, 1, &opts);
return self;
}
static VALUE rb_foo_sum(VALUE self) {
struct Foo* object = get_Foo(self);
// Initialize the value from the C POD object instance.
int sum = object->bar;
// We need an instance of the Class to call a class method.
VALUE klass = rb_const_get(rb_cObject, rb_intern("Foo"));
// Go into Ruby for the other values from class and instance
// variables.
sum += NUM2INT(rb_funcall(klass, rb_intern("bozo"), 0, 0));
// If the instance variable @baz is set then we'll sum this up
// as well. This can only be set through the options hash passed
// in through initialization.
if (rb_ivar_defined(self, rb_intern("baz")) == Qtrue) {
sum += NUM2INT(rb_iv_get(self, "@baz"));
}
// Yield to a block if one was supplied, otherwise return
// with the value.
if (rb_block_given_p()) {
return rb_yield(INT2NUM(sum));
}
// If we do not have a block just return the sum.
return INT2NUM(sum);
}
VALUE rb_define_class_Foo(void) {
// See the header file for this definition.
VALUE klass = rb_define_class("Foo", rb_cObject);
rb_define_alloc_func(klass, rb_foo_allocate);
rb_define_singleton_method(klass, "new", rb_foo_new, 1);
rb_define_private_method(klass, "initialize", rb_foo_initialize, 1);
rb_define_method(klass, "bar=", rb_foo_set_bar, 1);
rb_define_method(klass, "bar", rb_foo_get_bar, 0);
rb_define_singleton_method(klass, "bozo", rb_foo_get_bozo, 0);
rb_define_method(klass, "sum", rb_foo_sum, 0);
rb_cv_set(klass, "@@bozo", INT2NUM(1));
return klass;
}
#ifndef INCLUDED_FOO_H
#define INCLUDED_FOO_H
#include <ruby.h>
// Define a basic structure that we can use to bind a variable to. This
// is just to illustrate how to use the structure macros for that purpose.
struct Foo {
int bar;
};
// class Foo
// def initialize(opts = {})
// @bar = 42 || opts[:bar]
// @baz = opts[:baz] if opts[:baz]
// end
// def bar=(value)
// @bar = value
// end
// def bar
// @bar
// end
// def self.bozo
// @@bozo
// end
// def sum
// yield(@bar + @baz + @@bozo)
// end
// end
VALUE rb_define_class_Foo(void);
#endif
// $ gcc -I. -I$HOME/.rvm/rubies/ruby-1.9.3-p374/include/ruby-1.9.1 \
// > -L$HOME/.rvm/rubies/ruby-1.9.3-p374/lib -lruby-static \
// > -DHAVE_STRUCT_TIMESPEC -std=c99 main.c foo.c
#include <foo.h>
#include <ruby.h>
#include <stdio.h>
// Any errors are going to be stored in the global variable $!. So
// if we want to print this out we need to $!.to_s.
static void print_exception() {
VALUE ex = rb_gv_get("$!");
puts(RSTRING_PTR(rb_obj_as_string(ex)));
}
int main(int argc, char* argv[]) {
// See foo.h for the class definition.
const char script[] =
"foo = Foo.new({'bar' => 12})\n"
"puts \"Foo@@bozo value is #{Foo::bozo}\"\n"
"puts \"foo.bar initial value is #{foo.bar}\"\n"
"foo.bar = 10\n"
"puts \"foo.bar is now #{foo.bar}\"\n"
"foo.sum { |s| puts \"foo.sum block total is #{s}\" }\n"
"puts \"foo.sum method total is #{foo.sum}\"\n";
// Initialize the virtual machine here. Its all pretty basic.
RUBY_INIT_STACK;
ruby_init();
ruby_script("main");
// Create our object hierarchy.
VALUE rb_cFoo = rb_define_class_Foo();
// Evaluate the script and catch any errors.
int rc;
rb_eval_string_protect(script, &rc);
if (rc) {
print_exception();
return 1;
}
// Make sure to clean everything up.
ruby_finalize();
return 0;
}
LDFLAGS:= -L$(HOME)/.rvm/rubies/ruby-1.9.3-p374/lib -lruby-static
CFLAGS:=-I$(HOME)/.rvm/rubies/ruby-1.9.3-p374/include/ruby-1.9.1
all:
gcc -I. $(CFLAGS) $(LDFLAGS) -DHAVE_STRUCT_TIMESPEC -std=c99 foo.c main.c -o example
clean:
rm -f *.o example
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment