Slide 1

Slide 1 text

Extending Ruby Frederick Cheung CTO, dressipi.com @fglc2 / spacevatican.org Saturday, 28 September 13

Slide 2

Slide 2 text

Why? • Performance • Platform specific functionality • Access to best in class libraries Saturday, 28 September 13

Slide 3

Slide 3 text

Downsides • Slower to write - ruby is very concise and expressive • Generally harder • Memory management • ruby c api • Crashes harder when things go wrong Saturday, 28 September 13

Slide 4

Slide 4 text

Options • Native java code • “Classic” C-extension • SWIG • RubyInline • RICE • FFI Saturday, 28 September 13

Slide 5

Slide 5 text

Calling java from JRuby • Just do it - stupidly easy • JRuby handles most of the type conversion, method name conversion require 'java' java_import java.lang.System version = System.getProperties.get("java.runtime.version") version = System.properties["java.runtime.version"] Saturday, 28 September 13

Slide 6

Slide 6 text

• Can subclass java classes in ruby • Can implement a java interface in ruby • Rescue/raise java exceptions from ruby • Blocks handled nicely java.lang.Thread.new do puts 'hi' end Saturday, 28 September 13

Slide 7

Slide 7 text

Ruby Motion / MacRuby • Uses OS X BridgeSupport mechanism for extracting available methods/signatures • No manual work required • Only works on OS X Saturday, 28 September 13

Slide 8

Slide 8 text

C extensions • C code written using the C Ruby api - like the internals of ruby itself • Intricacies of the API not well documented (README.EXT, headers) • C api can change between versions (but then so can the ruby API) Saturday, 28 September 13

Slide 9

Slide 9 text

Compatibility • C-Ruby • Rubinius (compatibility with MRI C api is a design goal) • partial compatibility with jruby (disabled by default) Saturday, 28 September 13

Slide 10

Slide 10 text

What’s in a C extension? Saturday, 28 September 13

Slide 11

Slide 11 text

static VALUE rb_keychain_find(int argc, VALUE *argv, VALUE self){ ... } void Init_keychain(){ rb_cKeychain = rb_const_get(rb_cObject, rb_intern("Keychain")); rb_define_singleton_method(rb_cKeychain, "find", RUBY_METHOD_FUNC(rb_keychain_find), -1); } C functions attached to ruby objects Saturday, 28 September 13

Slide 12

Slide 12 text

static VALUE rb_keychain_find(int argc, VALUE *argv, VALUE self){ ... } void Init_keychain(){ rb_cKeychain = rb_const_get(rb_cObject, rb_intern("Keychain")); rb_define_singleton_method(rb_cKeychain, "find", RUBY_METHOD_FUNC(rb_keychain_find), -1); } C functions attached to ruby objects Saturday, 28 September 13

Slide 13

Slide 13 text

static VALUE rb_keychain_find(int argc, VALUE *argv, VALUE self){ ... } void Init_keychain(){ rb_cKeychain = rb_const_get(rb_cObject, rb_intern("Keychain")); rb_define_singleton_method(rb_cKeychain, "find", RUBY_METHOD_FUNC(rb_keychain_find), -1); } C functions attached to ruby objects Saturday, 28 September 13

Slide 14

Slide 14 text

Native data structures Person *person = ... VALUE ruby_object = Data_Wrap_Struct(rb_cPerson, NULL, free_person, person); Person *person=NULL; Data_Get_Struct(ruby_object, Person, person); Saturday, 28 September 13

Slide 15

Slide 15 text

Native data structures Person *person = ... VALUE ruby_object = Data_Wrap_Struct(rb_cPerson, NULL, free_person, person); Person *person=NULL; Data_Get_Struct(ruby_object, Person, person); Saturday, 28 September 13

Slide 16

Slide 16 text

Native data structures Person *person = ... VALUE ruby_object = Data_Wrap_Struct(rb_cPerson, NULL, free_person, person); Person *person=NULL; Data_Get_Struct(ruby_object, Person, person); Saturday, 28 September 13

Slide 17

Slide 17 text

Convert/check types • rb_float_new / RFLOAT_VALUE • CheckType(foo, T_STRING) • StringValueCStr(foo) • FIX2INT, INT2FIX, LL2NUM, ... • NIL_P • RTEST Saturday, 28 September 13

Slide 18

Slide 18 text

def find(first_or_all, kind, options={}) if options[:some_option] ... end end Gets verbose quickly Saturday, 28 September 13

Slide 19

Slide 19 text

static VALUE rb_keychain_find(int argc, VALUE *argv, VALUE self){ VALUE kind; VALUE options; VALUE first_or_all; rb_scan_args(argc, argv, "2:", &first_or_all, &kind, &options); Check_Type(first_or_all, T_SYMBOL); Check_Type(kind, T_STRING); if(options){ if(RTEST(rb_hash_aref(options, ID2SYM(rb_intern("some_option"))))) { ... } } } Saturday, 28 September 13

Slide 20

Slide 20 text

static VALUE rb_keychain_find(int argc, VALUE *argv, VALUE self){ VALUE kind; VALUE options; VALUE first_or_all; rb_scan_args(argc, argv, "2:", &first_or_all, &kind, &options); Check_Type(first_or_all, T_SYMBOL); Check_Type(kind, T_STRING); if(options){ if(RTEST(rb_hash_aref(options, ID2SYM(rb_intern("some_option"))))) { ... } } } Saturday, 28 September 13

Slide 21

Slide 21 text

static VALUE rb_keychain_find(int argc, VALUE *argv, VALUE self){ VALUE kind; VALUE options; VALUE first_or_all; rb_scan_args(argc, argv, "2:", &first_or_all, &kind, &options); Check_Type(first_or_all, T_SYMBOL); Check_Type(kind, T_STRING); if(options){ if(RTEST(rb_hash_aref(options, ID2SYM(rb_intern("some_option"))))) { ... } } } Saturday, 28 September 13

Slide 22

Slide 22 text

static VALUE rb_keychain_find(int argc, VALUE *argv, VALUE self){ VALUE kind; VALUE options; VALUE first_or_all; rb_scan_args(argc, argv, "2:", &first_or_all, &kind, &options); Check_Type(first_or_all, T_SYMBOL); Check_Type(kind, T_STRING); if(options){ if(RTEST(rb_hash_aref(options, ID2SYM(rb_intern("some_option"))))) { ... } } } Saturday, 28 September 13

Slide 23

Slide 23 text

static VALUE doSomething(VALUE yielded_object, VALUE rb_context,int argc, VALUE *argv){ Context *context = NULL; Data_Get_Struct( rb_context, Context, context); ... return Qnil; } Context *context = ... VALUE wrapped_struct = Data_Wrap_Struct(rb_cFindContext, NULL,NULL,context); rb_block_call(rb_cUser, rb_intern("find_each"), 0, NULL, RUBY_METHOD_FUNC(doSomething), wrapped_struct); Call a method with a block Saturday, 28 September 13

Slide 24

Slide 24 text

static VALUE doSomething(VALUE yielded_object, VALUE rb_context,int argc, VALUE *argv){ Context *context = NULL; Data_Get_Struct( rb_context, Context, context); ... return Qnil; } Context *context = ... VALUE wrapped_struct = Data_Wrap_Struct(rb_cFindContext, NULL,NULL,context); rb_block_call(rb_cUser, rb_intern("find_each"), 0, NULL, RUBY_METHOD_FUNC(doSomething), wrapped_struct); Call a method with a block Saturday, 28 September 13

Slide 25

Slide 25 text

static VALUE doSomething(VALUE yielded_object, VALUE rb_context,int argc, VALUE *argv){ Context *context = NULL; Data_Get_Struct( rb_context, Context, context); ... return Qnil; } Context *context = ... VALUE wrapped_struct = Data_Wrap_Struct(rb_cFindContext, NULL,NULL,context); rb_block_call(rb_cUser, rb_intern("find_each"), 0, NULL, RUBY_METHOD_FUNC(doSomething), wrapped_struct); Call a method with a block Saturday, 28 September 13

Slide 26

Slide 26 text

• Writing apis that are rubylike is difficult • Anything is possible but it can be quite laborious • Not much typesafety - nearly everything is a VALUE • Sometimes unhelpful api naming: rb_str_new, rb_str_new2, rb_str_new3, rb_str_new4, rb_str_new5 Saturday, 28 September 13

Slide 27

Slide 27 text

• Generates code for C extensions automatically from a marked up header file • Can target many languages (ruby, python, php, ocaml, perl, ...) • Generated code is pretty illegible • Interfaces often unnatural - need wrapping SWIG Saturday, 28 September 13

Slide 28

Slide 28 text

RubyInline • Write C extensions without some of the hassle • generates, compiles and loads extension at runtime • doesn’t work in irb Saturday, 28 September 13

Slide 29

Slide 29 text

require 'inline' class Factorial inline :C do |builder| builder.c_singleton <<-SRC long calculate(long n){ long result = 1; for(long i = 2; i<=n; i++){ result *= i; } return result; } SRC end end Saturday, 28 September 13

Slide 30

Slide 30 text

require 'inline' class Factorial inline :C do |builder| builder.c_singleton <<-SRC long calculate(long n){ long result = 1; for(long i = 2; i<=n; i++){ result *= i; } return result; } SRC end end Saturday, 28 September 13

Slide 31

Slide 31 text

require 'inline' class Factorial inline :C do |builder| builder.c_singleton <<-SRC long calculate(long n){ long result = 1; for(long i = 2; i<=n; i++){ result *= i; } return result; } SRC end end Saturday, 28 September 13

Slide 32

Slide 32 text

static VALUE calculate(VALUE self, VALUE _n) { long n = NUM2LONG(_n); long result = 1; for(long i = 2; i<=n; i++){ result *= i; } return LONG2NUM(result); } #ifdef __cplusplus extern "C" { #endif void Init_Inline_Factorial_7b051bd7f8f35cf0422f7470() { VALUE c = rb_cObject; c = rb_const_get(c, rb_intern("Factorial")); rb_define_singleton_method(c, "calculate", (VALUE(*)(ANYARGS))calculate, 1); } #ifdef __cplusplus } #endif Saturday, 28 September 13

Slide 33

Slide 33 text

static VALUE calculate(VALUE self, VALUE _n) { long n = NUM2LONG(_n); long result = 1; for(long i = 2; i<=n; i++){ result *= i; } return LONG2NUM(result); } #ifdef __cplusplus extern "C" { #endif void Init_Inline_Factorial_7b051bd7f8f35cf0422f7470() { VALUE c = rb_cObject; c = rb_const_get(c, rb_intern("Factorial")); rb_define_singleton_method(c, "calculate", (VALUE(*)(ANYARGS))calculate, 1); } #ifdef __cplusplus } #endif Saturday, 28 September 13

Slide 34

Slide 34 text

static VALUE calculate(VALUE self, VALUE _n) { long n = NUM2LONG(_n); long result = 1; for(long i = 2; i<=n; i++){ result *= i; } return LONG2NUM(result); } #ifdef __cplusplus extern "C" { #endif void Init_Inline_Factorial_7b051bd7f8f35cf0422f7470() { VALUE c = rb_cObject; c = rb_const_get(c, rb_intern("Factorial")); rb_define_singleton_method(c, "calculate", (VALUE(*)(ANYARGS))calculate, 1); } #ifdef __cplusplus } #endif Saturday, 28 September 13

Slide 35

Slide 35 text

module Foo inline do |builder| builder.add_compile_flags '-x c++', '-lstdc++', '-Wall', '-Werror' builder.prefix <<-SRC # line #{__LINE__ + 1} "#{__FILE__}" class foo { ... } SRC builder.c_raw <<-SRC VALUE attr_1(VALUE self){ foo *data = NULL; Data_Get_Struct( self, foo, data); return INT2FIX(data->get_attr_1()); } SRC end end Saturday, 28 September 13

Slide 36

Slide 36 text

module Foo inline do |builder| builder.add_compile_flags '-x c++', '-lstdc++', '-Wall', '-Werror' builder.prefix <<-SRC # line #{__LINE__ + 1} "#{__FILE__}" class foo { ... } SRC builder.c_raw <<-SRC VALUE attr_1(VALUE self){ foo *data = NULL; Data_Get_Struct( self, foo, data); return INT2FIX(data->get_attr_1()); } SRC end end Saturday, 28 September 13

Slide 37

Slide 37 text

module Foo inline do |builder| builder.add_compile_flags '-x c++', '-lstdc++', '-Wall', '-Werror' builder.prefix <<-SRC # line #{__LINE__ + 1} "#{__FILE__}" class foo { ... } SRC builder.c_raw <<-SRC VALUE attr_1(VALUE self){ foo *data = NULL; Data_Get_Struct( self, foo, data); return INT2FIX(data->get_attr_1()); } SRC end end Saturday, 28 September 13

Slide 38

Slide 38 text

module Foo inline do |builder| builder.add_compile_flags '-x c++', '-lstdc++', '-Wall', '-Werror' builder.prefix <<-SRC # line #{__LINE__ + 1} "#{__FILE__}" class foo { ... } SRC builder.c_raw <<-SRC VALUE attr_1(VALUE self){ foo *data = NULL; Data_Get_Struct( self, foo, data); return INT2FIX(data->get_attr_1()); } SRC end end Saturday, 28 September 13

Slide 39

Slide 39 text

Rubyinline - conclusion • Easy to deploy - no extra steps required • great for small hotspots / simple interfaces • raises CompilationError on failure - easy to include pure ruby & native version • Advantages diminish as size grows, eg in general no syntax highlighting, code completion. Saturday, 28 September 13

Slide 40

Slide 40 text

RICE • C++ library wrapping the ruby c api • automates some of the conversion boilerplate via templates • Can still call raw C api if needed Saturday, 28 September 13

Slide 41

Slide 41 text

long calculate_fibonacci(Object self, long n){ long result = 1, previous=0; for(long i=0; i< n-1;i++){ long old_previous = previous; previous = result; result = old_previous + result; } return result; } extern "C" void Init_fib(){ Class rb_cTest = define_class("Fibonnaci"). define_singleton_method("value", &calculate_fibonacci); } Saturday, 28 September 13

Slide 42

Slide 42 text

long calculate_fibonacci(Object self, long n){ long result = 1, previous=0; for(long i=0; i< n-1;i++){ long old_previous = previous; previous = result; result = old_previous + result; } return result; } extern "C" void Init_fib(){ Class rb_cTest = define_class("Fibonnaci"). define_singleton_method("value", &calculate_fibonacci); } Saturday, 28 September 13

Slide 43

Slide 43 text

long calculate_fibonacci(Object self, long n){ long result = 1, previous=0; for(long i=0; i< n-1;i++){ long old_previous = previous; previous = result; result = old_previous + result; } return result; } extern "C" void Init_fib(){ Class rb_cTest = define_class("Fibonnaci"). define_singleton_method("value", &calculate_fibonacci); } Saturday, 28 September 13

Slide 44

Slide 44 text

void some_method(String name, Object options_or_nil){ Hash options; if(!options_or_nil.is_nil()){ options = options_or_nil; } ... } extern "C" void Init_ext(){ Class rb_cTest = define_class("Test"). define_method("some_method",( Arg("name"), Arg("options_or_nil")=Nil)); } Optional arguments Saturday, 28 September 13

Slide 45

Slide 45 text

void some_method(String name, Object options_or_nil){ Hash options; if(!options_or_nil.is_nil()){ options = options_or_nil; } ... } extern "C" void Init_ext(){ Class rb_cTest = define_class("Test"). define_method("some_method",( Arg("name"), Arg("options_or_nil")=Nil)); } Optional arguments Saturday, 28 September 13

Slide 46

Slide 46 text

void some_method(String name, Object options_or_nil){ Hash options; if(!options_or_nil.is_nil()){ options = options_or_nil; } ... } extern "C" void Init_ext(){ Class rb_cTest = define_class("Test"). define_method("some_method",( Arg("name"), Arg("options_or_nil")=Nil)); } Optional arguments Saturday, 28 September 13

Slide 47

Slide 47 text

void some_method(String name, Object options_or_nil){ Hash options; if(!options_or_nil.is_nil()){ options = options_or_nil; } ... } extern "C" void Init_ext(){ Class rb_cTest = define_class("Test"). define_method("some_method",( Arg("name"), Arg("options_or_nil")=Nil)); } Optional arguments Saturday, 28 September 13

Slide 48

Slide 48 text

Type conversions Hash options = ... int value = options["amount"]; VALUE amount_ = rb_hash_aref(options, rb_str_new2("amount")); int amount = FIX2INT(amount_); OR Saturday, 28 September 13

Slide 49

Slide 49 text

Type conversions Hash options = ... int value = options["amount"]; VALUE amount_ = rb_hash_aref(options, rb_str_new2("amount")); int amount = FIX2INT(amount_); OR Saturday, 28 September 13

Slide 50

Slide 50 text

Type conversions Hash options = ... int value = options["amount"]; VALUE amount_ = rb_hash_aref(options, rb_str_new2("amount")); int amount = FIX2INT(amount_); OR Saturday, 28 September 13

Slide 51

Slide 51 text

Wrapping C++ objects extern "C" void Init_person(){ Data_Type rb_cPerson = define_class("Person"). define_constructor(Constructor()). define_method("name", &Person::get_name). define_method("name=",&Person::set_name). define_method("assimilate",&Person::assimilate). } Saturday, 28 September 13

Slide 52

Slide 52 text

Wrapping C++ objects extern "C" void Init_person(){ Data_Type rb_cPerson = define_class("Person"). define_constructor(Constructor()). define_method("name", &Person::get_name). define_method("name=",&Person::set_name). define_method("assimilate",&Person::assimilate). } Saturday, 28 September 13

Slide 53

Slide 53 text

Wrapping C++ objects extern "C" void Init_person(){ Data_Type rb_cPerson = define_class("Person"). define_constructor(Constructor()). define_method("name", &Person::get_name). define_method("name=",&Person::set_name). define_method("assimilate",&Person::assimilate). } Saturday, 28 September 13

Slide 54

Slide 54 text

class Person { public: Person():m_name(""){} const std::string& get_name() const{ return m_name; } void set_name(const std::string& new_name){ m_name = new_name; } void assimilate(Person other_person){ ... } private: std::string m_name; }; Saturday, 28 September 13

Slide 55

Slide 55 text

class Person { public: Person():m_name(""){} const std::string& get_name() const{ return m_name; } void set_name(const std::string& new_name){ m_name = new_name; } void assimilate(Person other_person){ ... } private: std::string m_name; }; Saturday, 28 September 13

Slide 56

Slide 56 text

class Person { public: Person():m_name(""){} const std::string& get_name() const{ return m_name; } void set_name(const std::string& new_name){ m_name = new_name; } void assimilate(Person other_person){ ... } private: std::string m_name; }; Saturday, 28 September 13

Slide 57

Slide 57 text

class Person { public: Person():m_name(""){} const std::string& get_name() const{ return m_name; } void set_name(const std::string& new_name){ m_name = new_name; } void assimilate(Person other_person){ ... } private: std::string m_name; }; Saturday, 28 September 13

Slide 58

Slide 58 text

• bridges C++ and ruby exceptions • implements STL iterators for Array, Hash • Loads of details at http://jasonroelofs.com/ 2010/02/23/how-rice-works/ RICE Saturday, 28 September 13

Slide 59

Slide 59 text

And now, for something completely different Saturday, 28 September 13

Slide 60

Slide 60 text

FFI • based on libffi • Ruby DSL for binding C functions / global variables • No compiler toolchain required, no C code to write • Supported by jruby, mri, rubinius Saturday, 28 September 13

Slide 61

Slide 61 text

require 'ffi' module C extend FFI::Library ffi_lib 'c' attach_function 'puts', [:string], :int end C.puts "Hello world" Saturday, 28 September 13

Slide 62

Slide 62 text

require 'ffi' module C extend FFI::Library ffi_lib 'c' attach_function 'puts', [:string], :int end C.puts "Hello world" Saturday, 28 September 13

Slide 63

Slide 63 text

require 'ffi' module C extend FFI::Library ffi_lib 'c' attach_function 'puts', [:string], :int end C.puts "Hello world" Saturday, 28 September 13

Slide 64

Slide 64 text

require 'ffi' module C extend FFI::Library ffi_lib 'c' attach_function 'puts', [:string], :int end C.puts "Hello world" Saturday, 28 September 13

Slide 65

Slide 65 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC callback :qsort_cmp, [:pointer, :pointer ], :int attach_function :qsort, [:pointer, :ulong, :ulong, :qsort_cmp], :int end def sort(array_of_ints) p = FFI::MemoryPointer.new(:int32, array_of_ints.size) p.put_array_of_int32(0, array_of_ints) LibC.qsort(p, array_of_ints.size, 4) do |p1, p2| i1 = p1.get_int32(0) i2 = p2.get_int32(0) i1 <=> i2 end p.get_array_of_int32(0, array_of_ints.size) end Saturday, 28 September 13

Slide 66

Slide 66 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC callback :qsort_cmp, [:pointer, :pointer ], :int attach_function :qsort, [:pointer, :ulong, :ulong, :qsort_cmp], :int end def sort(array_of_ints) p = FFI::MemoryPointer.new(:int32, array_of_ints.size) p.put_array_of_int32(0, array_of_ints) LibC.qsort(p, array_of_ints.size, 4) do |p1, p2| i1 = p1.get_int32(0) i2 = p2.get_int32(0) i1 <=> i2 end p.get_array_of_int32(0, array_of_ints.size) end Saturday, 28 September 13

Slide 67

Slide 67 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC callback :qsort_cmp, [:pointer, :pointer], :int attach_function :qsort, [:pointer, :ulong, :ulong, :qsort_cmp], :int end def sort(array_of_ints) p = FFI::MemoryPointer.new(:int32, array_of_ints.size) p.put_array_of_int32(0, array_of_ints) LibC.qsort(p, array_of_ints.size, 4) do |p1, p2| i1 = p1.get_int32(0) i2 = p2.get_int32(0) i1 <=> i2 end p.get_array_of_int32(0, array_of_ints.size) end Saturday, 28 September 13

Slide 68

Slide 68 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC callback :qsort_cmp, [:pointer, :pointer], :int attach_function :qsort, [:pointer, :ulong, :ulong, :qsort_cmp], :int end def sort(array_of_ints) p = FFI::MemoryPointer.new(:int32, array_of_ints.size) p.put_array_of_int32(0, array_of_ints) LibC.qsort(p, array_of_ints.size, 4) do |p1, p2| i1 = p1.get_int32(0) i2 = p2.get_int32(0) i1 <=> i2 end p.get_array_of_int32(0, array_of_ints.size) end Saturday, 28 September 13

Slide 69

Slide 69 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC class Timezone < FFI::Struct layout :tz_minuteswest, :int, :tz_dsttime, :int end class Timeval < FFI::Struct layout :tv_sec, :time_t, :tv_usec, :suseconds_t end attach_function :gettimeofday, [Timeval, Timezone], :int end out = LibC::Timeval.new LibC.gettimeofday(out, nil) puts "the time according to libc is #{out[:tv_sec]}" Saturday, 28 September 13

Slide 70

Slide 70 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC class Timezone < FFI::Struct layout :tz_minuteswest, :int, :tz_dsttime, :int end class Timeval < FFI::Struct layout :tv_sec, :time_t, :tv_usec, :suseconds_t end attach_function :gettimeofday, [Timeval, Timezone], :int end out = LibC::Timeval.new LibC.gettimeofday(out, nil) puts "the time according to libc is #{out[:tv_sec]}" Saturday, 28 September 13

Slide 71

Slide 71 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC class Timezone < FFI::Struct layout :tz_minuteswest, :int, :tz_dsttime, :int end class Timeval < FFI::Struct layout :tv_sec, :time_t, :tv_usec, :suseconds_t end attach_function :gettimeofday, [Timeval, Timezone], :int end out = LibC::Timeval.new LibC.gettimeofday(out, nil) puts "the time according to libc is #{out[:tv_sec]}" Saturday, 28 September 13

Slide 72

Slide 72 text

module LibC extend FFI::Library ffi_lib FFI::Library::LIBC class Timezone < FFI::Struct layout :tz_minuteswest, :int, :tz_dsttime, :int end class Timeval < FFI::Struct layout :tv_sec, :time_t, :tv_usec, :suseconds_t end attach_function :gettimeofday, [Timeval, Timezone], :int end out = LibC::Timeval.new LibC.gettimeofday(out, nil) puts "the time according to libc is #{out[:tv_sec]}" Saturday, 28 September 13

Slide 73

Slide 73 text

Beyond toy examples • OS X Keychain wrapper written using C api, FFI, RICE • https://github.com/fcheung/keychain ~ 1000 lines of ruby, ~ 600 lines of real code • https://github.com/fcheung/keychain_c/ ~ 700 lines of C + a small amount of ruby to autogenerate accessors • https://github.com/fcheung/keychain_rice ~ 800 lines of C++ (no auto generated accessors) • All versions pass the same set of specs Saturday, 28 September 13

Slide 74

Slide 74 text

def unlock! password=nil if password password = password.encode(Encoding::UTF_8) status = Sec.SecKeychainUnlock self, password.bytesize, password, 1 else status = Sec.SecKeychainUnlock self, 0, nil, 0 end Sec.check_osstatus status end FFI Saturday, 28 September 13

Slide 75

Slide 75 text

RICE void Keychain::unlock(const Object password_or_nil){ OSStatus result = 0; if(password_or_nil.test()){ String password = password_or_nil.call("encode","UTF-8"); result = SecKeychainUnlock(m_keychain, (UInt32)password.length(), (UInt8*)password.c_str(), true); } else{ result = SecKeychainUnlock(m_keychain,0,NULL,false); } CheckOSStatusOrRaise(result); } Saturday, 28 September 13

Slide 76

Slide 76 text

C static VALUE rb_keychain_unlock(int argc, VALUE *argv, VALUE self){ OSStatus result = noErr; VALUE password; SecKeychainRef keychain=NULL; Data_Get_Struct(self, struct OpaqueSecKeychainRef, keychain); rb_scan_args(argc, argv, "01", &password); if(password){ StringValue(password); password = rb_str_export_to_enc(password, rb_utf8_encoding()); result = SecKeychainUnlock(keychain, (UInt32)RSTRING_LEN(password), (UInt8*)RSTRING_PTR(password), true); }else{ result = SecKeychainUnlock(keychain,0,NULL,false); } CheckOSStatusOrRaise(result); return Qnil; } Saturday, 28 September 13

Slide 77

Slide 77 text

• Great when you have a C library to wrap • Small performance penalty over a C extension (often irrelevant) • Much easier/nicer (in my experience) than writing C extensions • Handles many difficult edge cases FFI summary Saturday, 28 September 13

Slide 78

Slide 78 text

The good stuff • No knowledge of MRI C api required • Better compatibility across implementations • All glue code is ruby - easier to write and easier to be ruby-like • less typechecking/wrapping/unwrapping boilerplate • FFI yielded more reusable components (core_foundation gem) Saturday, 28 September 13

Slide 79

Slide 79 text

But... • Takes a little getting used to • Constants / enums / #defines are a pain • you still need to understand C, memory management • Some incompatibilities between FFI implementations • harder to create narrow slices of functionality • Still crashes just as hard if you mess it up Saturday, 28 September 13

Slide 80

Slide 80 text

Other languages • C is the lingua franca • Wrap with C, then use previous techniques Saturday, 28 September 13

Slide 81

Slide 81 text

Objective-C • Not handled by ffi out of the box • Can write wrapper library exposing C entry points Saturday, 28 September 13

Slide 82

Slide 82 text

Or... • Objective-C method calls are just calls to objc_msgSend • Objective-C supports run time introspection of classes, methods etc. • Can we use some ruby metaprogramming to bridge the two? Saturday, 28 September 13

Slide 83

Slide 83 text

Thank You! • [email protected] • @fglc2 • github.com/fcheung • http://spacevatican.org Saturday, 28 September 13