Upload
rivierarb
View
1.380
Download
8
Embed Size (px)
DESCRIPTION
Presented at the Ruby Drink-up of Sophia Antipolis on the 17th of April 2012 by Muriel Salvan (@MurielSalvan).
Citation preview
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
Why Ruby and C ?
Ruby without C
C without Ruby
Ruby and C combined
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...)
Now You can write your own C extensions easily
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.
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...)
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
How to write your C extension
Default Ruby program structure
myapp/ bin/ exec.rb lib/ myapp/ mylib.rb ext/ myapp/ myext.c extconf.rb myotherlib/ otherlib.c extconf.rb
Write the C fileext/myapp/myext.c
#include "ruby.h"
void Init_myext() {
printf("Hello Ruby from C!\n");
}
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
How to compile and use your C extension
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
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
And now, package it in a nice Ruby gem!
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!
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
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
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)
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
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
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
Need more than a Hello World ?
=> The Ruby C API
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);}
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;}
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);}
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!
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!'
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
Links
Makefile generation options:
Linuxtopia tutorial
mkmf rdoc
Ruby C API:
Eqqon article
Matz' Readme
Metaprogramming
FFI gem
This presentation is available under CC-BY license by Muriel Salvan
Q/A