Slide 1

Slide 1 text

Calling native code from Ruby eurucamp 2015 Boris Bügling - @NeoNacho

Slide 2

Slide 2 text

We want to use some existing native library or system functionality

Slide 3

Slide 3 text

We don't want a C extension

Slide 4

Slide 4 text

Because nobody got time to deal with user's compiler problems

Slide 5

Slide 5 text

Xcodeproj • Part of CocoaPods • Interacts with Xcode projects • Used to include a C extension

Slide 6

Slide 6 text

Xcodeproj issues $ bundle exec rake ... linking shared-object xcodeproj_ext.bundle clang: error: unknown argument: '-multiply_definedsuppress' [-Wunused-command-line-argument-hard-error-in-future] clang: note: this will be a hard error (cannot be downgraded to a warning) in the future make: *** [xcodeproj_ext.bundle] Error 1

Slide 7

Slide 7 text

Xcodeproj issues $ gem install xcodeproj Building native extensions. This could take a while... ERROR: Error installing xcodeproj: ERROR: Failed to build gem native extension. /Users/siuying/.rvm/rubies/ruby-1.9.3-p194/bin/ruby extconf.rb checking for -std=c99 option to compiler... *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options.

Slide 8

Slide 8 text

Xcodeproj issues creating Makefile make [...] make install /usr/bin/install -c -m 0755 xcodeproj_ext.bundle ./.gem.20130406-29555-zd0wy8 ERROR: While executing gem ... (NoMethodError) undefined method `join' for nil:NilClass

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Fiddle!

Slide 11

Slide 11 text

Somewhat exists in Ruby 1.9.x

Slide 12

Slide 12 text

But you really want 2.x

Slide 13

Slide 13 text

FFI for calling C from Ruby

Slide 14

Slide 14 text

Uses dlopen / dlsym

Slide 15

Slide 15 text

Can also be used to call Objective- C !

Slide 16

Slide 16 text

require 'fiddle' image = Fiddle.dlopen(PATH) function = Fiddle::Function.new(image[symbol], parameter_types, return_type) result = function.call(a, b, c)

Slide 17

Slide 17 text

Types • Fiddle::TYPE_VOID • Fiddle::TYPE_INT • Fiddle::TYPE_CHAR • Fiddle::TYPE_VOIDP • ...

Slide 18

Slide 18 text

Use Fiddle::Handle.new to refer to our own binary

Slide 19

Slide 19 text

real-world example Xcodeproj

Slide 20

Slide 20 text

let's look at some code Using CoreFoundation

Slide 21

Slide 21 text

def self.extern_image(image, symbol, parameter_types, return_type) symbol = symbol.to_s create_function = symbol.include?('Create') function_cache_key = "@__#{symbol}__" define_singleton_method(symbol) do |*args| unless function = instance_variable_get(function_cache_key) function = Fiddle::Function.new(image[symbol], parameter_types, return_type) instance_variable_set(function_cache_key, function) end result = function.call(*args) create_function ? CFAutoRelease(result) : result end end

Slide 22

Slide 22 text

PATH = '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation' def self.image @image ||= Fiddle.dlopen(PATH) end def self.extern(symbol, parameter_types, return_type) extern_image(image, symbol, parameter_types, return_type) end extern :CFDataGetLength, [CFTypeRef], CFIndex extern :CFDataGetBytePtr, [CFTypeRef], VoidPointer def self.CFStringToRubyString(string) data = CFStringCreateExternalRepresentation(NULL, string, KCFStringEncodingUTF8, 0) if data.null? raise TypeError, 'Unable to convert CFStringRef.' end bytes_ptr = CFDataGetBytePtr(data) result = bytes_ptr.to_str(CFDataGetLength(data)) result.force_encoding(Encoding::UTF_8) result end

Slide 23

Slide 23 text

let's look at some code Using private Xcode functionality

Slide 24

Slide 24 text

def self.objc_msgSend(args, return_type = CoreFoundation::VoidPointer) arguments = [CoreFoundation::VoidPointer, CoreFoundation::VoidPointer] + args Fiddle::Function.new(image['objc_msgSend'], arguments, return_type) end class CFDictionary < NSObject public def initialize(dictionary) @dictionary = dictionary end def plistDescriptionUTF8Data selector = 'plistDescriptionUTF8Data' return nil unless NSObject.respondsToSelector(@dictionary, selector) plistDescriptionUTF8Data = CFDictionary.objc_msgSend([]) plistDescriptionUTF8Data.call( @dictionary, CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString(selector))) end end

Slide 25

Slide 25 text

Recap • C extensions have a bad UX • Fiddle provides a way to use native code dynamically • Which eliminates all the compilation hassle • We can even call Objective-C if we want

Slide 26

Slide 26 text

@NeoNacho [email protected] http://buegling.com/talks