One thing that many software developers neglect is the build/buy decision. Often it is more cost effective to buy an existing solution rather than build your own. This can happen at the macro level as well – inside your custom-built solution, problems may be solved using libraries written by others. If that library happens to be open source, all the better!
But what if the best solution out there is written in some other language? I ran into this very scenario when I came across an ideal open source solution written in C++ that I wanted to use in my Rails application. The solution implements a very complex data structure and support code, and would add zero value to my application by trying to do it from scratch. There is nothing like that library in the Rails ecosystem.
To the Rescue: Foreign Function Interface, FFI. This is, in itself, a very impressive piece of work. With just a few lines of code to bind the interfaces, you can literally call natively compiled code from Ruby.
You add a gem to your Gemfile:
gem 'ffi'
Then develop a bridge between the rails and c++ spaces.
- Declare all external interfaces in a .h file for C++ to name/externalize them properly
- Use FFI’s DSL to declare the bridge specifics
The header file (simplified for presentation):
extern "C" { void addBlock(Index *idx, int64_t start_day, int64_t start_hour, int64_t end_day, int64_t end_hour, int64_t id); int64_t get_right(return_right_down* rrd); int64_t get_down(return_right_down* rrd); int64_t get_corner(return_right_down* rrd); }
The DSL:
module SpecialLibrary extend FFI::Library ffi_lib 'c' ffi_lib_flags :now, :global ffi_lib './lib/special/libspecial_schedtool.o' attach_function :addBlock, [:pointer, :int, :int, :int, :int, :int], :void attach_function :get_right, [:pointer], :int attach_function :get_down, [:pointer], :int attach_function :get_corner, [:pointer], :int end
The functions declared in the header file are implemented by you in a C++ file and compiled to a .o declared using the ffl_lib syntax in the DSL above; those functions call C++ library API entries in your leveraged library (libspecial in my case), which is built according to the supplier and installed on the system as a library.
If that’s not impressive enough, it works on Ubuntu and OSX. The commands to do the C++ compilation of the interfaces is different, but the code itself needs no changes. I put this little script together to build on either system:
#! /bin/bash # # Different commands on OSX v Linux case `uname` in 'Darwin') g++ -std=c++0x -I/usr/local/include/ -L/usr/local/lib libspecial_schedtool.cpp -lspecialindex_c -lspecialindex -dynamiclib -o libspecial_schedtool.o ;; 'Linux') g++ -std=c++0x -fPIC -I/usr/local/include/ -L/usr/local/lib libspecial_schedtool.cpp -lspecialindex_c -lspecialindex -shared -o libspecial_schedtool.o ;; *) Echo "Error: don't know how to compile on this platform" esac
Honestly, my app would not exist today without this leverage.