Slide 1

Slide 1 text

Building C Extensions in Ruby André Medeiros @superdealloc

Slide 2

Slide 2 text

Why?

Slide 3

Slide 3 text

Ruby is not slow (don’t believe the haters)

Slide 4

Slide 4 text

Not great for heavy processing.

Slide 5

Slide 5 text

It’s not hard.

Slide 6

Slide 6 text

Ruby’s C API feels a lot like Ruby

Slide 7

Slide 7 text

Don’t know C?
 Learn it The Hard Way http://c.learncodethehardway.org/

Slide 8

Slide 8 text

How to approach the problem.

Slide 9

Slide 9 text

C types as first class citizens Great for gems that involve back-and-forth communication (RMagick, Nokogiri)

Slide 10

Slide 10 text

Ruby first Better for when it’s one-sided communication (MySQL2, statistics, log parsing)

Slide 11

Slide 11 text

Getting started

Slide 12

Slide 12 text

Create your gem skeleton $ bundle gem speedy_gem!

Slide 13

Slide 13 text

Add the Rake compile task # speedy_gem.gemspec! spec.add_development_dependency "rake-compiler"! ! # Rakefile! require "rake/extensiontask"! ! Rake::ExtensionTask.new("speedy_gem") do |ext|! extension.lib_dir = "lib/speedy_gem"! end!

Slide 14

Slide 14 text

Create THE EXT FOLDER $ mkdir -p ext/speedy_gem!

Slide 15

Slide 15 text

… and your extension configuration file # ext/speedy_gem/extconf.rb! require 'mkmf'! ! create_makefile('speedy_gem')!

Slide 16

Slide 16 text

Boilerplate

Slide 17

Slide 17 text

initializing your extension // ext/speedy_gem/speedy_gem.c! ! #include "ruby.h"! ! VALUE SpeedyGemClass = Qnil;! ! void Init_rubyconf() {! // Initialization code goes here! }!

Slide 18

Slide 18 text

Defining your first class // ext/speedy_gem/speedy_gem.c! ! #include "ruby.h"! ! VALUE SpeedyGemClass = Qnil;! ! void Init_rubyconf() {! SpeedyGemClass = rb_define_class("SpeedyGem", rb_cObject);! }!

Slide 19

Slide 19 text

Memory management // ext/speedy_gem/speedy_gem.c! ! #include "ruby.h"! ! VALUE SpeedyGemClass = Qnil;! ! void Init_rubyconf() {! SpeedyGemClass = rb_define_class("SpeedyGem", rb_cObject);! rb_define_alloc_func(SpeedyGemClass, rb_alloc);! }! ! VALUE rb_alloc(VALUE self) {! char *string = calloc(1024, sizeof(char));! return Data_Wrap_Struct(SpeedyGemClass, NULL, rb_free, string);! }! ! void rb_free(char *string) {! free(string);! }!

Slide 20

Slide 20 text

Getting the C struct // ext/speedy_gem/speedy_gem.c! ! #include "ruby.h"! ! VALUE SpeedyGemClass = Qnil;! ! void Init_rubyconf() {! SpeedyGemClass = rb_define_class("SpeedyGem", rb_cObject);! rb_define_method(SpeedyGemClass, "string", rb_get_string, 0);! }! ! VALUE rb_get_string(VALUE self) {! char *string;! Data_Get_Struct(self, char, string);! return rb_str_new2(string);! }!

Slide 21

Slide 21 text

your first method // ext/speedy_gem/speedy_gem.c! ! #include "ruby.h"! ! VALUE SpeedyGemClass = Qnil;! ! void Init_rubyconf() {! SpeedyGemClass = rb_define_class("SpeedyGem", rb_cObject);! rb_define_method(SpeedyGemClass, "say_hi", rb_say_hi, 1);! }! ! VALUE rb_say_hi(VALUE self, VALUE name) {! VALUE phrase = rb_str_new2("Hello, ");! rb_str_append(phrase, name);! return phrase;! } !

Slide 22

Slide 22 text

Checking types

Slide 23

Slide 23 text

built-in types // Checks if radius is a Float! Check_Type(radius, T_FLOAT);! ! // Checks if age is a Fixnum! Check_Type(age, T_FIXNUM);! ! // Checks if name is a String! Check_Type(name, T_STRING);! ! // Checks if options is a Hash! Check_Type(options, T_HASH); !

Slide 24

Slide 24 text

Others if (CLASS_OF(klass) != SpeedyGemClass) {! rb_raise(rb_eTypeError, "wrong argument type %s (expected SpeedyGem)",! rb_obj_classname(klass));! }!

Slide 25

Slide 25 text

Method Parameters

Slide 26

Slide 26 text

Fixed number // ext/speedy_gem/speedy_gem.c! ! #include "ruby.h"! ! VALUE SpeedyGemClass = Qnil;! ! void Init_rubyconf() {! SpeedyGemClass = rb_define_class("SpeedyGem", rb_cObject);! rb_define_method(SpeedyGemClass, "say_hi", rb_say_hi, 1);! }! ! VALUE rb_say_hi(VALUE self, VALUE name) {! VALUE phrase = rb_str_new2("Hello, ");! rb_str_append(phrase, name);! return phrase;! } !

Slide 27

Slide 27 text

optional // ext/speedy_gem/speedy_gem.c! ! #include "ruby.h"! ! VALUE SpeedyGemClass = Qnil;! ! void Init_rubyconf() {! SpeedyGemClass = rb_define_class("SpeedyGem", rb_cObject);! rb_define_method(SpeedyGemClass, "say_hi", rb_say_hi, -1);! }! ! VALUE rb_say_hi(int argc, VALUE *argv, VALUE self) {! VALUE phrase = rb_str_new2("Hello, ");! VALUE name;! rb_scan_args(argc, argv, "01", &name);! ! if(!RTEST(name))! name = rb_str_new2("World");! ! rb_str_append(phrase, name);! return phrase;! } !

Slide 28

Slide 28 text

Honing Ruby functionality

Slide 29

Slide 29 text

Strings // Create a Ruby string from char *! VALUE phrase = rb_str_new2("Hello, ");! ! // Append two Ruby strings together! rb_str_append(phrase, subject);! ! // Append a char * to a Ruby string! rb_str_cat2(phrase, "Something");! ! // Duplicate a Ruby string! VALUE copy = rb_str_dup(phrase);! ! // Get a substring from a Ruby string! VALUE without_hello = rb_str_substr(phrase, 7, 100);!

Slide 30

Slide 30 text

Arrays // Create a Ruby array! VALUE array = rb_ary_new();! ! // Push values to a Ruby array! rb_ary_push(array, rb_str_new2("One thing"));! rb_ary_push(array, rb_str_new2("Another thing"));! ! // Add a value to a specific position in a Ruby array! rb_ary_store(array, 2, rb_str_new2("A third thing"));! ! // Reverse a Ruby array! VALUE reversed = rb_ary_reverse(array);! ! // Create a Ruby string out of a Ruby array! VALUE string = rb_ary_join(array, rb_str_new2(", "));!

Slide 31

Slide 31 text

Hashes // Creating a Ruby hash! VALUE hash = rb_hash_new();! ! // Adding values! rb_hash_aset(! hash, // The Ruby hash! ID2SYM(rb_intern("universal_answer")), // Key! INT2NUM(42) // Value! );! ! // Getting a value from a Ruby hash! VALUE answer = rb_hash_aref(hash, ID2SYM(rb_intern("universal_answer")));!

Slide 32

Slide 32 text

Blocks // Check if a block was given and yield a value if so.! if(rb_block_given_p()) rb_yield(value)! ! // Same as above, but multiple values! if(rb_block_given_p()) rb_yield_values(value1, value2)! ! // Store a block into a variable and call it later.! VALUE block = rb_block_proc();! rb_funcall(block, rb_intern("call"), 2, value1, value2);!

Slide 33

Slide 33 text

SYMBOLS ID2SYM(rb_intern("some_symbol"))!

Slide 34

Slide 34 text

And a bunch more! andremedeiros/ruby-c-cheat-sheet

Slide 35

Slide 35 text

Good Practices

Slide 36

Slide 36 text

Define what your API looks like first

Slide 37

Slide 37 text

Return self as much as possible

Slide 38

Slide 38 text

Provide bang methods as much as possible.

Slide 39

Slide 39 text

Ruby doesn’t always map 1:1 to C libraries