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.

Boris Bügling

July 31, 2015
Tweet

More Decks by Boris Bügling

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. We don't want a C extension

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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.

    View Slide

  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

    View Slide

  9. View Slide

  10. Fiddle!

    View Slide

  11. Somewhat exists in Ruby 1.9.x

    View Slide

  12. But you really want 2.x

    View Slide

  13. FFI for calling C from Ruby

    View Slide

  14. Uses dlopen / dlsym

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. real-world example
    Xcodeproj

    View Slide

  20. let's look at some code
    Using CoreFoundation

    View Slide

  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

    View Slide

  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

    View Slide

  23. let's look at some code
    Using private Xcode functionality

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide