Extending Ruby with C
Garrett Rooney
CollabNet
Why extend Ruby?
- Speed
- Don't reinvent the wheel
- Morbid desire to deal with segfaults, memory corruption, etc.
The Big Secret
- It's not that hard
- Writing C extensions is...
- Seriously, the APIs feel a lot like Ruby code
How Do I Build It?
- mkmf module
- Traditionally goes in a file named extconf.rb
- Ruby code that generates makefiles to do the work
Hello World extconf.rb
require 'mkmf'
dir_config("hello")
create_makefile("hello")
Let's Try It Out
$ ruby extconf.rb
creating Makefile
$ make
gcc -fPIC -Wall -g -O2 -fPIC -I. -I/usr/lib/ruby/1.8/i486-linux -I/usr/lib/ruby/1.8/i486-linux -I. -c hello.c
gcc -shared -L"/usr/lib" -o hello.so hello.o -lruby1.8 -lpthread -ldl -lcrypt -lm -lc
$ ls
extconf.rb hello.c hello.o hello.so* Makefile
$ irb
irb(main):001:0> require 'hello'
LoadError: Failed to lookup Init function ./hello.so
from (irb):1:in `require'
from (irb):1
irb(main):002:0>
What Went Wrong?
- Ruby extensions are simple shared objects
- Ruby uses dlopen (or the equivalent) to access them
- Tries to call a function called Init_modulename
- So for our hello module it's Init_hello
- That function sets up the module
The Minimal Extension
#include "ruby.h"
void
Init_hello()
{
/* nothing here yet */
}
Another Shot
$ ruby extconf.rb
creating Makefile
$ make
gcc -fPIC -Wall -g -O2 -fPIC -I. -I/usr/lib/ruby/1.8/i486-linux -I/usr/lib/ruby/1.8/i486-linux -I. -c hello.c
gcc -shared -L"/usr/lib" -o hello.so hello.o -lruby1.8 -lpthread -ldl -lcrypt -lm -lc
$ ls
extconf.rb hello.c hello.o hello.so* Makefile
$ irb
irb(main):001:0> require 'hello'
=> true
irb(main):002:0>
Digression: Useful mkmf Functions
- dir_config has optional arguments:
- Second is include path(s)
- Third is library path(s)
- have_library('foo') # -lfoo
Creating a Class
- Call functions that define modules/classes:
- rb_define_module - Creates modules
- rb_define_class_under - Creates a new class
- rb_define_alloc_func - Lets you control how allocation happens
An Example With Class
#include "ruby.h"
static VALUE rb_mHello;
static VALUE rb_cHelloWorld;
void
Init_hello()
{
rb_mHello = rb_define_module("Hello");
rb_cHelloWorld = rb_define_class_under(rb_mHello,
"World",
rb_cObject);
/* Hold off on rb_define_alloc_func... */
}
Example: Hello::World.new
$ irb
irb(main):001:0> require 'hello'
=> true
irb(main):002:0> h = Hello::World.new
=> #<Hello::World:0xb7cf2b50>
irb(main):003:0>
Digression: What's a Value?
- C extensions are filled with VALUE variables
- Represent either:
- Numberic value (i.e. Fixnum)
- Pointer to an object
- Find what it is with Check_Type(VALUE val, int type)
- Check_Type(val, T_STRING) - Is it a String?
- Check_Type(val, T_OBJECT) - Is it an Object?
Shortcuts
- FIXNUM_P(val) - Is it a Fixnum?
- NIL_P(val) - Is it nil?
Classes That Do Stuff
- Your class is going to need to carry data around
- You need Data_Wrap_Struct and Data_Get_Struct
- Data_Wrap_Struct:
- Takes a C level pointer and shoves it into a VALUE
- Data_Get_Struct:
- Gets it back out again later
Digression: Garbage Collection
- Ruby has a mark and sweep garbage collector
- This is why it's more fun to code in than C ;-)
- Objects need to be built to participate in it
-
Mark callback that marks other Ruby objects that this object
points to via rb_gc_mark
- Free callback that frees this object's data
-
rb_global_variable - If you have C globals that refer to Ruby
Objects
Using Data_Wrap_Struct 1
static void
file_mark(FILE *f)
{
/* Nothing needed */
}
static void
file_free(FILE *f)
{
if (f)
fclose(f);
}
static VALUE
file_alloc(VALUE klass)
{
FILE *f = NULL;
return Data_Wrap_Struct(klass, file_mark, file_free, f);
}
Using Data_Wrap_Struct 2
static VALUE
file_init(VALUE obj, VALUE path)
{
DATA_PTR(obj) = fopen(RSTRING(path)->ptr, "r");
return Qnil;
}
void
Init_hello()
{
rb_mHello = rb_define_module("Hello");
rb_cMyFile = rb_define_class_under(rb_mHello,
"MyFile",
rb_cObject);
rb_define_alloc_func(rb_cMyFile, file_alloc);
rb_define_method(rb_cMyFile, "initialize", file_init, 1);
}
Lets Try It
$ irb
irb(main):001:0> require 'hello'
=> true
irb(main):002:0> h = Hello::MyFile.new("hello.c")
=> #<Hello::MyFile:0xb7cf2b50>
irb(main):003:0>
Writing Methods
- Now we need to hook some methods up
- rb_define_method
- Takes a class, function name, function pointer and integer
- The integer tells Ruby what arguments the function expects
- Postitive means that's the number of arguments
- -1 means int argc, VALUE *args, VALUE self
The World's Stupidest Method
static VALUE
print_string(VALUE self, VALUE string)
{
Check_Type(string, T_STRING);
printf("%s", RSTRING(string)->ptr);
return Qnil;
}
void
Init_hello()
{
rb_mHello = rb_define_module("Hello");
rb_cWorld = rb_define_class_under(rb_mHello,
"World",
rb_cObject);
rb_define_method(rb_cWorld, "print_string", print_string, 1);
}
Dealing with Strings
- Lots of functions for messing with strings:
- rb_str_new(const char *ptr, long len)
- rb_str_new2(const char *ptr)
- rb_str_cat(VALUE str, const char *ptr, long len)
- rb_str_modify(VALUE str)
- Or poke around inside:
- RSTRING(str)->len
- RSTRING(str)->ptr
- Alternatively:
- StringValuePtr(object)
- StringValueLen(object)
-
These work on things that are kinda-sorta strings, i.e. they
act like strings, but don't have type T_STRING
- Works by calling the to_str method on the object
Dealing With Arrays
- Create them with
- rb_ary_new()
- rb_ary_new2(long count)
- rb_ary_new3(long count, ...)
- rb_ary_new4(long count, VALUE *elts)
- Mess with them via:
- rb_ary_push(VALUE ary, VALUE val)
- rb_ary_pop(VALUE ary)
- rb_ary_shift(VALUE ary)
- rb_ary_unshift(VALUE ary, VALUE val)
- Or more low level...
- rb_ary_store(VALUE ary, long idx, VALUE elt)
- rb_ary_entry(VALUE ary, long idx)
Dealing With Hashes
- Hashes are much the same...
- Create via rb_hash_new()
- Get contents via rb_hash_aref(VALUE hash, VALUE key)
- Set contents via rb_hash_aset(VALUE hash, VALUE key, VALUE val)
- Remove elements via rb_hash_delete(VALUE hash, VALUE key)
- Count number of elements via rb_hash_size(VALUE hash)
Calling Ruby Methods
- rb_funcall - Calls a method
- First arg is the invocant
- Second is the method
- Third is the number of arguments
- Rest are the arguments
- rb_intern - Gets you the ID of the method
- rb_respond_to - Checks if an object implements a method
Method Call Example
static VALUE
write_to_file(VALUE self, VALUE file)
{
ID method = rb_intern("<<");
if (! rb_respond_to(file, rb_intern("<<")))
rb_raise(rb_eRuntimeError, "target must respond to '<<'");
rb_funcall(file, method, 1, self);
return Qtrue;
}
Exception Handling
- As we saw, rb_raise raises an exception.
- rb_rescue handles the other side of things
-
Four arguments:
- Function to call
- VALUE to pass to it
- Function to call on exception
- VALUE to pass to it
Exceptions Example
static VALUE handle_exception(VALUE unused) {
return Qfalse;
}
static VALUE call_write(VALUE ary) {
rb_funcall(rb_ary_entry(ary, 0), rb_intern("<<"), 1, rb_ary_entry(ary, 1));
return Qtrue;
}
static genxStatus writer_send(void *baton, constUtf8 s) {
VALUE ary, file = (VALUE) baton;
if (! rb_respond_to(file, rb_intern("<<")))
rb_raise(rb_eRuntimeError, "target must respond to '<<'");
ary = rb_ary_new2(2);
rb_ary_store(ary, 0, file);
rb_ary_store(ary, 1, rb_str_new2((const char *) s));
if (rb_rescue(call_write, ary, handle_exception, Qnil) == Qfalse)
return GENX_IO_ERROR;
else
return GENX_SUCCESS;
}
Extensions and Gems
- Basically just works
- Gem::Specifications#extensions << "path/to/extconf.rb"
- Means it'll be compiled at install time
- Building precompiled gems seems to be a bit of a black art...
Fun with SWIG
- Too lazy to write an actual extension?
- Let SWIG do it for you!
- Looks at a C API and generates C code for an extension
- Sound too good to be true?
- It is :(
- Not perfect
- Requires manual wrapping to get decent APIs
- Still pretty cool
Example: SWIG Interface
%module libc
int fread(void *, size_t, size_t, FILE *);
int fwrite(void *, size_t, size_t, FILE *);
void *malloc(size_t);
Example: Building SWIG Code
$ swig -ruby libc.i
# produces libc_wrap.c
$ ruby extconf.rb
# produces a Makefile
$ make
# produces the extension, libc.so
Example: Using SWIG modules
require 'libc'
f1 = Libc.fopen("source.txt", "r")
f2 = Libc.fopen("dest.txt", "w+")
buffer = Libc.malloc(1024)
nread = Libc.fread(buffer, 1, 1024, f1)
while nread > 0
Libc.fwrite(buffer, 1, nread, f2)
nread = Libc.fread(buffer, 1, 1024, f1)
end
# dest.txt now contains the contents of source.txt
Fun with RubyInline
- RubyInline lets you embed other languages in a Ruby script
- Somewhat limited:
-
Can only convert arguments of char, unsigned, unsigned int,
char *, int, long, and unsigned long
- Otherwise you need to write manual wrapper code
- Good parlor trick though ;-)
- http://www.zenspider.com/ZSS/Products/RubyInline/
Example: RubyInline
require 'inline'
class Copier
inline do |builder|
builder.c <<EOF
void copy_file(const char *source, const char *dest) {
FILE *source_f = fopen(source, "r");
FILE *dest_f = fopen(dest, "w+");
char buffer[1024];
int nread = fread(buffer, 1, 1024, source_f);
while (nread > 0) {
fwrite(buffer, 1, nread, dest_f);
nread = fread(buffer, 1, 1024, source_f);
}
}
EOF
end
end
c = Copier.new
c.copy_file("source.txt", "dest.txt")
# the contents of source.txt are copied to dest.txt
Wrap Up
- Any Questions?
- Go forth and extend Ruby!
- Thank You