Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Calling Native Code from Ruby

Calling Native Code from Ruby

This talk gives a quick introduction into the not so well known Fiddle, which has been a part of MRI since 1.9. It allows direct interaction with C code via a Foreign Function Interface, which enables wrapping native code without compiling C extensions, making the lives of users of your projects much easier.

9d2ea021919ff81e02d48530aae191bd?s=128

Boris Bügling

July 31, 2015
Tweet

Transcript

  1. Calling native code from Ruby eurucamp 2015 Boris Bügling -

    @NeoNacho
  2. We want to use some existing native library or system

    functionality
  3. We don't want a C extension

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

  5. Xcodeproj • Part of CocoaPods • Interacts with Xcode projects

    • Used to include a C extension
  6. 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
  7. 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.
  8. 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
  9. None
  10. Fiddle!

  11. Somewhat exists in Ruby 1.9.x

  12. But you really want 2.x

  13. FFI for calling C from Ruby

  14. Uses dlopen / dlsym

  15. Can also be used to call Objective- C !

  16. require 'fiddle' image = Fiddle.dlopen(PATH) function = Fiddle::Function.new(image[symbol], parameter_types, return_type)

    result = function.call(a, b, c)
  17. Types • Fiddle::TYPE_VOID • Fiddle::TYPE_INT • Fiddle::TYPE_CHAR • Fiddle::TYPE_VOIDP •

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

  19. real-world example Xcodeproj

  20. let's look at some code Using CoreFoundation

  21. 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
  22. 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
  23. let's look at some code Using private Xcode functionality

  24. 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
  25. 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
  26. @NeoNacho boris@contentful.com http://buegling.com/talks