1. Writing a Gem
with native extensions
Tristan Penman
Melbourne Ruby Meetup, August 2018
A whirlwind tour
2. What are they?
Native extensions allow you to extend the functionality
available in Ruby by writing code in natively compiled
languages such as C, C++, or even Rust.
This talk shows how native extensions can be used to
interact with a third-party library written in C.
The final code from this talk can be found on Github:
https://github.com/tristanpenman/simple-clipboard
Native extensions
A brief introduction
3. Native extensions
Examples
• 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.
4. libclipboard
libclipboard is a cross-platform clipboard library
Simple C-based API:
clipboard_new - create a context through which to access
the user's clipboard
clipboard_free - free any memory allocated by
clipboard_new
clipboard_text - read the contents of the clipboard as text
clipboard_set_text - replace the contents of the clipboard
with new text
6. simple_clipboard
We will use a native extension to wrap libclipboard with a
Module that we can use in Ruby code:
module SimpleClipboard
def get_text
#TODO: Return current contents ofclipboard,ornil
raise NotImplementedError
end
def set_text(new_text)
#TODO: Update clipboard; return previous contents, ornil
raise NotImplementedError
end
end
7. Extending Ruby using C
To create our native extension we need two files:
extconf.rb
simple_clipboard.c
8. Extending Ruby using C
require 'mkmf'
$LOCAL_LIBS << '-lclipboard'
if RUBY_PLATFORM =~ /darwin/
$LDFLAGS <<' -framework AppKit'
end
create_header
create_makefile 'simple_clipboard/simple_clipboard'
extconf.rb
9. Extending Ruby using C
simple_clipboard.c (1/2)
#include <ruby.h>
#include <libclipboard.h>
VALUEget_text(VALUE _self) {
VALUE result = Qnil;
char *text = clipboard_text(cb);
if (text) {
result =rb_str_new(text, strlen(text));
free(text);
}
return result;
}
// continued on next slide...
10. Extending Ruby using C
simple_clipboard.c (2/2)
// continued from previous slide
VALUEset_text(VALUE _self, VALUE str) {
// omitted, since it is similar to get_text
}
voidInit_simple_clipboard() {
VALUE m =rb_define_module("SimpleClipboard");
rb_define_module_function(m, "get_text", get_text, 0);
rb_define_module_function(m, "set_text", set_text, 1);
}
11. Run 'ruby extconf.rb' to generate:
Header file (extconf.h, which is redundant in this case)
Makefile
(also mkmf.log)
Run ’make’ to compile the extension
On Mac OS X, creates a .bundle file
On Linux, creates a .so
On Windows, creates a .dll
Extending Ruby using C
12. 2.3.3:001> require './simple_clipboard'
=>true
2.3.3:002> SimpleClipboard.get_text
=>"Extending Ruby using C"
2.3.3:003> SimpleClipboard.set_text "Hello world"
=>"Extending Ruby using C"
2.3.3:004> SimpleClipboard.get_text
=>"Hello world"
Extending Ruby using C
IRB session
13. Extending Ruby using C
In a nutshell
For an extension named 'xyz' we need:
An extconf.rb file and source file called 'xyz.c'
In the 'xyz.c' file, a function called Init_xyz
A native extension is free to:
define modules, classes and methods that operate on
Ruby values, via an opaque C datatype 'VALUE'
Call existing Ruby code
Crash the current process
And more generally, call 'undefined behavior'
14. How to include C code in a gem
Okay, safety be damned, we want performance…
or legacy functionality…
or something.
15. lib/
simple_clipboard/
version.rb
simple_clipboard.rb
simple_clipboard.gemspec
How to include C code in a gem
Layout without a native extension
16. How to include C code in a gem
Layout with a native extension
ext/
simple_clipboard/
extconf.rb
simple_clipboard.c
lib/
simple_clipboard/
version.rb
simple_clipboard.rb
simple_clipboard.gemspec
17. #Boilerplate omitted
Gem::Specification.new do |s|
s.name ='simple_clipboard'
s.version =SimpleClipboard::VERSION
s.date ='2018-07-24'
s.summary ='Simple clipboardexample gem'
s.authors =['TristanPenman']
s.email ='tristan@tristanpenman.com'
s.licenses =['MIT']
#continued on next slide...
How to include C code in a gem
simple_clipboard.gemspec (1/2)
18. #continued from previous slide...
s.extensions =['ext/simple_clipboard/extconf.rb']
#Tell bundler where to findthe code forour gem
s.require_paths =['lib']
#Files toinclude in bundle
s.files =['ext/simple_clipboard/simple_clipboard.c',
'lib/simple_clipboard.rb',
'lib/simple_clipboard/version.rb']
end
How to include C code in a gem
simple_clipboard.gemspec (2/2)
19. How to include C code in a gem
Run 'gem build simple_clipboard.gemspec':
Does not actually compile native extension
Creates 'simple_clipboard-0.0.1.gem'
Run 'gem install simple_clipboard-0.0.1.gem'
This is when bundler will build the native extension
And this why, when things go wrong while building gems
such as nokigiri, that you can get very complex error
messages
20. $ gem install simple_clipboard-0.0.1.gem
How to include C code in a gem
Example
Building native extensions. This could take a while...
Successfully installed simple_clipboard-0.0.1
Parsing documentation forsimple_clipboard-0.0.1
Done installing documentation for simple_clipboard after 0 seconds
1 gem installed
21. 2.3.3 :001 >require 'simple_clipboard'
=> true
2.3.3 :002 > SimpleClipboard.get_text
=> "Extending Ruby using C"
2.3.3 :003 > SimpleClipboard.set_text "Hello world"
=> "Extending Ruby using C"
2.3.3 :004 > SimpleClipboard.get_text
=> "Hello world"
Extending Ruby using C
IRB session
22. Testing native extensions
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require 'rake/extensiontask'
desc "simple_clipboard unit tests"
RSpec::Core::RakeTask.new(:spec) do|t|
t.pattern ="spec/*_spec.rb"
t.verbose =true
End
#continued on next slide...
Rakefile (1/2)
24. Testing native extensions
1. Run 'rake' to compile and run tests
2. Run 'rake compile' to only compile native extension
Compiles the native extension, then copies
simple_clipboard.[bundle|so|dll] file into 'lib/simple_clipboard'
3. Run 'rake spec' to only run tests
Assumes that native extension (e.g. simple_clipboard.bundle) has
already been copied to 'lib/simple_clipboard' directory
Rake tasks
25. Resources
• Useful reference implementation of a
Gem with a native extension:
https://github.com/neilslater/ruby_nex_c
• Core documentation:
https://ruby-doc.org/core-
2.3.3/doc/extension_rdoc.html
(Beware the version number in this link)
• Pat Shaughnessy’s book:
Ruby Under a Microscope
26. Resources
• RubyGems documentation
https://guides.rubygems.org/gems-with-extensions/
• Aaron Bedra's Extending Ruby guide
http://aaronbedra.com/extending-ruby
• 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
(12 parts in total)