KEMBAR78
Extending Ruby using C++ | PPTX
Extending Ruby using C++
Tristan Penman
Melbourne C++ Meetup, September 2018
Ruby essentials
What is Ruby?
 Ruby is a dynamically-typed, object-oriented language
 Garbage collection built into the VM
 Several implementations exist
 MRI – Matz's Ruby Interpreter (a.k.a. CRuby)
 JRuby – JVM based implementation
 Rubinius – Implemented in C++ and Ruby
 Ruby libraries are typically packaged as Gems
 Gems that need to use C or C++ typically do so via native
extensions
Ruby essentials
class Person
attr_reader :name, :age
def initialize(name, age) # constructor
@name, @age =name, age
end
def <=>(person) #the comparison operator for sorting
@age <=> person.age
end
def to_s #returns string representation of Person
"#{@name} (#{@age})"
end
end
Example code (1/2)
Ruby essentials
Example code (2/2)
#continued fromprevious slide
group=[
Person.new("Bob", 33),
Person.new("Chris", 16),
Person.new("Ash", 23)
]
puts group.sort.reverse
Bob(33)
Ash (23)
Chris(16)
Expected output:
Ruby essentials
Popular gems with native extensions
• Byebug - a debugger for Ruby, that uses Ruby's TracePoint API
for execution control and the Debug Inspector API for call
stack navigation. Written as a C extension for speed.
• nokogiri - an HTML and XML parser. Uses native libraries for
speed and ensure standards compliance.
• RMagick - bindings for the ImageMagick image manipulation
library.
• sqlite3 - bindings for the SQLite3 database engine.
Ruby essentials
Installing gems
$ gem install <gem_name>
To install the 'ffi' gem, which we'll use in the first example:
$ gem install ffi
This gem includes native extensions, so output will look like:
Fetching: ffi-1.9.25.gem (100%)
Building native extensions. Thiscould take a while...
Successfully installed ffi-1.9.25
Parsing documentation forffi-1.9.25
Installing ridocumentation forffi-1.9.25
Done installing documentation for ffi after 27 seconds
1 gem installed
Options for extending Ruby
• Foreign function interface
– Technique for accessing shared libraries that follow C-style
calling convention
• Inlined C / C++ code
– Compiles native code at runtime
• Native extensions with C and C++
– Precompiled
• All example code is available at:
https://github.com/tristanpenman/ruby-cpp-examples
Foreign function interface
require 'ffi'
module Simon
#Include ffi functionality as a 'mixin'
extend FFI::Library
#Link with libc
ffi_lib 'c'
#Define afunction that takes astring (char *) andprints it
attach_function :says, :puts, [:string ],:int
end
Simon.says 'Hello'
simon_ffi.rb
Foreign function interface
Limitations
• Generally requires that code is available as a shared library on
the user's operating system
• That library needs to export an API that follow the C-style
calling convention (__cdecl)
• Link errors may occur at runtime, rather than when a gem is
first installed
• In the simple case, 'ffi' gem defines methods as belonging to a
Ruby module
– Not appropriate for exposing C++ classes in Ruby
Inlined C / C++ code
require 'inline'
module Simon
inline(:C) do |builder|
builder.add_compile_flags'-xc++', '-lstdc++'
builder.include '<iostream>'
builder.c_singleton '
void says(const char *str) {
std::cout <<str <<std::endl;
}'
end
end
Simon.says 'Hello'
simon_inline.rb
Inlined C++ code
Limitations
• Incurs runtime compile overhead
• Semantics of RubyInline gem are a bit tricky
– Moving beyond defining individual methods becomes
much more complicated
– Best documentation for this happens to 'inline.rb' in the
RubyInline source code
Native extensions
• Address some of the limitations of FFI and inline C++ code
techniques
• Compiled prior to runtime, but they are generally subject to
rigorous runtime requirements
• Typically implemented using the Ruby C API
• Our simple example requires two files:
– simon_native.c, which contains the implementation code
– extconf.rb, to generate a Makefile
• simon_native.c must include Init_simon_native(), which is
called by the Ruby VM to load the extension
Native extensions
#include <ruby.h>
#include <stdio.h>
VALUE says(VALUE _self, VALUE str) {
Check_Type(str, T_STRING);
puts(StringValueCStr(str));
return Qnil;
}
voidInit_simon_native() {
VALUE mod=rb_define_module("Simon");
const intnum_args =1;
rb_define_module_function(mod, "says", says, num_args);
}
simon_native.c
Native extensions
require 'mkmf'
#Generates aMakefile tocompile simon_native.c intoa bundle
#that can be loaded intoa Ruby VM
create_makefile 'simon_native'
extconf.rb
Running 'make' should generate a bundle that can be loaded into
your Ruby program like so:
require './simon_native'
Simon.says 'Hello'
Hello
Native extensions
Limitations
• Requires code to be written in C
– Assumptions around considerations such as file extensions
• Uses Ruby C API, which is not terribly user friendly
• What about C++?
Native extensions with C++
Using the 'rice' gem
• Rice is a C++ wrapper for Ruby's C API
• Provides classes and templates that make the Ruby API easier
and safer to use
• Also provides template functions that can take an existing C++
class and make it available to Ruby code
• Minor changes to extconf.rb are required
Native extensions with C++
simon_native_rice.cpp
#include <iostream>
#include "rice/Module.hpp"
using namespace Rice;
void says(const char *str) {
std::cout <<str << std::endl;
}
extern "C"
voidInit_simon_native_rice() {
define_module("Simon")
.define_module_function("says", &says);
}
Native extensions with C++
extconf.rb
require 'mkmf-rice'
#Assumes the presence ofa file named 'simon_native_rice.cpp'
create_makefile 'simon_native_rice'
Native extensions with C++
Wrapping C++ classes
• Say we have an existing C++ class that we want to make
accessible via Ruby
• Declared in simon.hpp:
#pragmaonce
#include <iostream>
class Simon {
void says(const char * str) {
std::cout <<str << std::endl;
}
}
Wrapping C++ classes
simon_native_rice_wrapper.cpp
#include "rice/Constructor.hpp"
#include "rice/Data_Type.hpp"
#include "simon.hpp"
using namespace Rice;
extern "C"
voidInit_simon_native_rice_wrapper() {
Data_Type<Simon> rb_cSimon =
define_class<Simon>("Simon")
.define_constructor(Constructor<Simon>())
.define_method("says", &Simon::says);
}
Native extensions with C++
extconf.rb
require 'mkmf-rice' #instead of'mkmf'
create_makefile 'simon_native_rice_wrapper'
require './simon_native_rice_wrapper'
simon =Simon.new
simon.says 'Hello'
After running 'make', usage is similar to the example for the
'RubyInline' gem:
Resources
• Core documentation:
https://ruby-doc.org/core-
2.3.3/doc/extension_rdoc.html
(Beware the version number in this link)
• Aaron Bedra's Extending Ruby guide
http://aaronbedra.com/extending-ruby
• Pat Shaughnessy’s book:
Ruby Under a Microscope
Resources
• IBM's Building Ruby extensions in C++ using Rice
https://www.ibm.com/developerworks/library/os-
extendruby/index.html
• Chris Lalancette's in-depth series on writing Ruby extensions
in C, which covers numerous topics:
http://clalance.blogspot.com/2011/01/writing-ruby-
extensions-in-c-part-1.html
Thanks for listening

Extending Ruby using C++

  • 1.
    Extending Ruby usingC++ Tristan Penman Melbourne C++ Meetup, September 2018
  • 2.
    Ruby essentials What isRuby?  Ruby is a dynamically-typed, object-oriented language  Garbage collection built into the VM  Several implementations exist  MRI – Matz's Ruby Interpreter (a.k.a. CRuby)  JRuby – JVM based implementation  Rubinius – Implemented in C++ and Ruby  Ruby libraries are typically packaged as Gems  Gems that need to use C or C++ typically do so via native extensions
  • 3.
    Ruby essentials class Person attr_reader:name, :age def initialize(name, age) # constructor @name, @age =name, age end def <=>(person) #the comparison operator for sorting @age <=> person.age end def to_s #returns string representation of Person "#{@name} (#{@age})" end end Example code (1/2)
  • 4.
    Ruby essentials Example code(2/2) #continued fromprevious slide group=[ Person.new("Bob", 33), Person.new("Chris", 16), Person.new("Ash", 23) ] puts group.sort.reverse Bob(33) Ash (23) Chris(16) Expected output:
  • 5.
    Ruby essentials Popular gemswith native extensions • Byebug - a debugger for Ruby, that uses Ruby's TracePoint API for execution control and the Debug Inspector API for call stack navigation. Written as a C extension for speed. • nokogiri - an HTML and XML parser. Uses native libraries for speed and ensure standards compliance. • RMagick - bindings for the ImageMagick image manipulation library. • sqlite3 - bindings for the SQLite3 database engine.
  • 6.
    Ruby essentials Installing gems $gem install <gem_name> To install the 'ffi' gem, which we'll use in the first example: $ gem install ffi This gem includes native extensions, so output will look like: Fetching: ffi-1.9.25.gem (100%) Building native extensions. Thiscould take a while... Successfully installed ffi-1.9.25 Parsing documentation forffi-1.9.25 Installing ridocumentation forffi-1.9.25 Done installing documentation for ffi after 27 seconds 1 gem installed
  • 7.
    Options for extendingRuby • Foreign function interface – Technique for accessing shared libraries that follow C-style calling convention • Inlined C / C++ code – Compiles native code at runtime • Native extensions with C and C++ – Precompiled • All example code is available at: https://github.com/tristanpenman/ruby-cpp-examples
  • 8.
    Foreign function interface require'ffi' module Simon #Include ffi functionality as a 'mixin' extend FFI::Library #Link with libc ffi_lib 'c' #Define afunction that takes astring (char *) andprints it attach_function :says, :puts, [:string ],:int end Simon.says 'Hello' simon_ffi.rb
  • 9.
    Foreign function interface Limitations •Generally requires that code is available as a shared library on the user's operating system • That library needs to export an API that follow the C-style calling convention (__cdecl) • Link errors may occur at runtime, rather than when a gem is first installed • In the simple case, 'ffi' gem defines methods as belonging to a Ruby module – Not appropriate for exposing C++ classes in Ruby
  • 10.
    Inlined C /C++ code require 'inline' module Simon inline(:C) do |builder| builder.add_compile_flags'-xc++', '-lstdc++' builder.include '<iostream>' builder.c_singleton ' void says(const char *str) { std::cout <<str <<std::endl; }' end end Simon.says 'Hello' simon_inline.rb
  • 11.
    Inlined C++ code Limitations •Incurs runtime compile overhead • Semantics of RubyInline gem are a bit tricky – Moving beyond defining individual methods becomes much more complicated – Best documentation for this happens to 'inline.rb' in the RubyInline source code
  • 12.
    Native extensions • Addresssome of the limitations of FFI and inline C++ code techniques • Compiled prior to runtime, but they are generally subject to rigorous runtime requirements • Typically implemented using the Ruby C API • Our simple example requires two files: – simon_native.c, which contains the implementation code – extconf.rb, to generate a Makefile • simon_native.c must include Init_simon_native(), which is called by the Ruby VM to load the extension
  • 13.
    Native extensions #include <ruby.h> #include<stdio.h> VALUE says(VALUE _self, VALUE str) { Check_Type(str, T_STRING); puts(StringValueCStr(str)); return Qnil; } voidInit_simon_native() { VALUE mod=rb_define_module("Simon"); const intnum_args =1; rb_define_module_function(mod, "says", says, num_args); } simon_native.c
  • 14.
    Native extensions require 'mkmf' #GeneratesaMakefile tocompile simon_native.c intoa bundle #that can be loaded intoa Ruby VM create_makefile 'simon_native' extconf.rb Running 'make' should generate a bundle that can be loaded into your Ruby program like so: require './simon_native' Simon.says 'Hello' Hello
  • 15.
    Native extensions Limitations • Requirescode to be written in C – Assumptions around considerations such as file extensions • Uses Ruby C API, which is not terribly user friendly • What about C++?
  • 16.
    Native extensions withC++ Using the 'rice' gem • Rice is a C++ wrapper for Ruby's C API • Provides classes and templates that make the Ruby API easier and safer to use • Also provides template functions that can take an existing C++ class and make it available to Ruby code • Minor changes to extconf.rb are required
  • 17.
    Native extensions withC++ simon_native_rice.cpp #include <iostream> #include "rice/Module.hpp" using namespace Rice; void says(const char *str) { std::cout <<str << std::endl; } extern "C" voidInit_simon_native_rice() { define_module("Simon") .define_module_function("says", &says); }
  • 18.
    Native extensions withC++ extconf.rb require 'mkmf-rice' #Assumes the presence ofa file named 'simon_native_rice.cpp' create_makefile 'simon_native_rice'
  • 19.
    Native extensions withC++ Wrapping C++ classes • Say we have an existing C++ class that we want to make accessible via Ruby • Declared in simon.hpp: #pragmaonce #include <iostream> class Simon { void says(const char * str) { std::cout <<str << std::endl; } }
  • 20.
    Wrapping C++ classes simon_native_rice_wrapper.cpp #include"rice/Constructor.hpp" #include "rice/Data_Type.hpp" #include "simon.hpp" using namespace Rice; extern "C" voidInit_simon_native_rice_wrapper() { Data_Type<Simon> rb_cSimon = define_class<Simon>("Simon") .define_constructor(Constructor<Simon>()) .define_method("says", &Simon::says); }
  • 21.
    Native extensions withC++ extconf.rb require 'mkmf-rice' #instead of'mkmf' create_makefile 'simon_native_rice_wrapper' require './simon_native_rice_wrapper' simon =Simon.new simon.says 'Hello' After running 'make', usage is similar to the example for the 'RubyInline' gem:
  • 22.
    Resources • Core documentation: https://ruby-doc.org/core- 2.3.3/doc/extension_rdoc.html (Bewarethe version number in this link) • Aaron Bedra's Extending Ruby guide http://aaronbedra.com/extending-ruby • Pat Shaughnessy’s book: Ruby Under a Microscope
  • 23.
    Resources • IBM's BuildingRuby extensions in C++ using Rice https://www.ibm.com/developerworks/library/os- extendruby/index.html • Chris Lalancette's in-depth series on writing Ruby extensions in C, which covers numerous topics: http://clalance.blogspot.com/2011/01/writing-ruby- extensions-in-c-part-1.html
  • 24.