Ruby C extensions at the Ruby drink-up of Sophia, April 2012

35
C extensions easy in Ruby C extensions easy in Ruby Apr 17 th 2012 Muriel Salvan Open Source Lead developer and architect X-Aeon Solutions http://x-aeon.com

description

Presented at the Ruby Drink-up of Sophia Antipolis on the 17th of April 2012 by Muriel Salvan (@MurielSalvan).

Transcript of Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Page 1: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

C extensions easy in RubyC extensions easy in Ruby

Apr 17th 2012Muriel Salvan

Open Source Lead developer and architectX-Aeon Solutions

http://x-aeon.com

Page 2: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Why Ruby and C ?

Page 3: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Ruby without C

Page 4: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

C without Ruby

Page 5: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Ruby and C combined

Page 6: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Ruby and C are already coupled

Ruby core: around 100 classes written in C (String, Rational, Marshal, IO...)

Ruby standard libs: 35 libs written in C: (BigDecimal, Date, OpenSSL...)

Page 7: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Now You can write your own C extensions easily

Page 8: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

What is a C extension ?

A requirable library from Ruby code ... require 'myext'

… that can use any other Ruby's object or library,

… written in C,

… and compiled.

Page 9: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Technically speaking

A C extension is a compiled library (.so or .dll) that:

defines 1 specific C function,

is accessible in Ruby's load path (same as other .rb files)

It is used the same way Ruby's libraries (.rb) are (packaging, search path, require...)

Page 10: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

What do you need to write C extensions ?

A C development environment ([g]cc, ld, [g]make...)

Already installed on *nix

Cygwin, MinGW, Ruby DevKit on Windows

Little C knowledge

Some Ruby's C API knowledge

Page 11: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

How to write your C extension

Page 12: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Default Ruby program structure

myapp/ bin/ exec.rb lib/ myapp/ mylib.rb ext/ myapp/ myext.c extconf.rb myotherlib/ otherlib.c extconf.rb

Page 13: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Write the C fileext/myapp/myext.c

#include "ruby.h"

void Init_myext() {

printf("Hello Ruby from C!\n");

}

Page 14: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Write the extconf fileext/myapp/extconf.rb

require 'mkmf'

create_makefile('myext')

And that's it!

Your C extension is ready to be compiled and used

Page 15: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

How to compile and use your C extension

Page 16: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Compile itin ext/myapp/

=> ruby extconf.rb

creating Makefile

=> make

gcc -I. -I/usr/lib/ruby/1.8/i386-cygwin -I/usr/lib/ruby/1.8/i386-cygwin -I. -g -O2 -c myext.c

gcc -shared -s -o myext.so myext.o -L. -L/usr/lib -L. -Wl,--enable-auto-image-base,--enable-auto-import,--export-all -lruby -ldl -lcrypt

=> ext/myapp/Makefile

=> ext/myapp/myext.so

Page 17: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Use itbin/exec.rb

#!/bin/env ruby

puts 'Before requiring C extension'

require 'myapp/myext'

puts 'After requiring C extension'

=> ruby -Iext bin/exec.rb

Before requiring C extensionHello Ruby from C!After requiring C extension

Page 18: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

And now, package it in a nice Ruby gem!

Page 19: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

First flavor:Package the compiled

extension

Add the compiled extension to the files list (like any other library)

Add your ext/ directory as a required path

Don't forget to set your Gem platform as specific!

Page 20: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Platform dependent: gem spec

myapp.compiled.gemspec.rbGem::Specification.new do |spec| spec.name = 'my_app_compiled' spec.version = '0.1' spec.summary = 'Summary' spec.author = 'me' spec.bindir = 'bin' spec.executable = 'exec.rb'

spec.files = [ 'bin/exec.rb', 'ext/myapp/myext.so' ]

spec.platform = Gem::Platform::CURRENT spec.require_path = 'ext'end

=> gem build myapp.compiled.gemspec.rb Successfully built RubyGem Name: my_app_compiled Version: 0.1 File: my_app_compiled-0.1-x86-cygwin.gem

Page 21: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Platform dependent:Install and run it

=> gem install my_app_compiled-0.1-x86-cygwin.gem

Successfully installed my_app_compiled-0.1-x86-cygwin

1 gem installedInstalling ri documentation for my_app_compiled-0.1-x86-cygwin...

Installing RDoc documentation for my_app_compiled-0.1-x86-cygwin...

=> exec.rbBefore requiring C extensionHello Ruby from C!After requiring C extension

Page 22: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Second flavor:Platform independent

packaging

Add the C extension source files to the files list

Add your ext/ directory as a required path

Keep your Gem platform as Ruby

Register the C extension (path to the extconf.rb file)

Page 23: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Platform independent: gem spec

myapp.gemspec.rbGem::Specification.new do |spec| spec.name = 'my_app' # { ... } spec.executable = 'exec.rb'

spec.files = [ 'bin/exec.rb', 'ext/myapp/myext.c', 'ext/myapp/extconf.rb' ]

spec.platform = Gem::Platform::RUBY spec.require_path = 'ext' spec.extensions = [ 'ext/myapp/extconf.rb' ]

end=> gem build myapp.gemspec.rb Successfully built RubyGem Name: my_app Version: 0.1 File: my_app-0.1.gem

Page 24: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Platform independent:Install and run it

=> gem install my_app-0.1.gem Building native extensions. This could take a while...

Successfully installed my_app-0.11 gem installedInstalling ri documentation for my_app-0.1...Installing RDoc documentation for my_app-0.1...

=> exec.rbBefore requiring C extensionHello Ruby from C!After requiring C extension

Page 25: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Which flavor the best ?

Platform dependent:

Need to release 1 Ruby gem per platform (need to compile on each platform)

Users do not need any development environment

Platform independent:

Need to release just 1 Ruby gem

Users must have a C development environment to install it

Page 26: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Need more than a Hello World ?

=> The Ruby C API

Page 27: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

module MyModule class MyClass def my_method(param1, param2,

param3) end endend

static VALUE myclass_mymethod( VALUE rb_self, VALUE rb_param1, VALUE rb_param2, VALUE rb_param3) {}

void Init_myext() { VALUE mymodule = rb_define_module("MyModule"); VALUE myclass = rb_define_class_under(mymodule,

"MyClass", rb_cObject); rb_define_method(myclass, "my_method",

myclass_mymethod, 3);}

Page 28: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

if param1 == nil puts 'Param1 is nil' return nilelse return param1 + 42end

if (rb_param1 == Qnil) { rb_funcall(rb_self, rb_intern("puts"), 1,

rb_str_new2("Param1 is nil")); return Qnil;} else { int param1 = FIX2INT(rb_param1); VALUE result = INT2FIX(param1 + 42); return result;}

Page 29: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

param2.each do |elem| elemstr = elem.to_s elemstr[0] = 'A' puts elemstr[0..3]end

int nbrelems = RARRAY(rb_param2)->len;int idx;for (idx = 0; idx < nbrelems; ++idx) { VALUE rb_elem = rb_ary_entry(rb_param2, idx); VALUE rb_elemstr = rb_funcall(rb_elem,

rb_intern("to_s"), 0); char* elemstr = RSTRING_PTR(rb_elemstr); elemstr[0] = 'A'; char* substr = (char*)malloc(5); strncpy(substr, elemstr, 4); substr[4] = '\0'; rb_funcall(rb_self, rb_intern("puts"), 1,

rb_str_new2(substr)); free(substr);}

Page 30: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

param3.block_method(3) do |block_param|

puts param1 + block_paramend

static VALUE call_block_method(VALUE rb_params) { VALUE rb_object = rb_ary_entry(rb_params, 0); VALUE rb_value = rb_ary_entry(rb_params, 1); return rb_funcall(rb_object, rb_intern("block_method"), 1, rb_value);}

static VALUE yielded_block(VALUE rb_yield_params, VALUE rb_iterate_params) {

VALUE rb_block_param = rb_yield_params; VALUE rb_self = rb_ary_entry(rb_iterate_params, 0); VALUE rb_param1 = rb_ary_entry(rb_iterate_params, 1); return rb_funcall(rb_self, rb_intern("puts"), 1,

INT2FIX(FIX2INT(rb_block_param)+FIX2INT(rb_param1)));}

rb_iterate( call_block_method, rb_ary_new3(2, rb_param3, INT2FIX(3)), yielded_block, rb_ary_new3(2, rb_self, rb_param1));

Thanks Matz for Ruby!

Page 31: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Using external compiled libraries

=> FFI gem

Page 32: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

FFI gem

Import external functions from a compiled library into a Ruby module

require 'ffi'

module MyLib extend FFI::Library ffi_lib 'c' attach_function :puts, [ :string ], :intend

MyLib.puts 'Hello, World using libc!'

Page 33: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

FFI features

It has a very intuitive DSL

It supports all C native types

It supports C structs (also nested), enums and global variables

It supports callbacks

It has smart methods to handle memory management of pointers and structs

Page 35: Ruby C extensions at the Ruby drink-up of Sophia, April 2012

Q/A