Rubinius' Foreign Function Interface
I really, really, really love Rubinius’ Foreign Function Interface (FFI) since it allows you to replace C code with Ruby code. Earlier today I wrote
Socket::getaddrinfo in C for Rubinius, and just now I finished a rewrite using FFI and Ruby. I’ve commented the code for clarity.
def self.getaddrinfo(host, service, family = nil, socktype = nil, protocol = nil, flags = nil) service = service.to_s # MemoryPointer.new is kind-of like malloc(3), but understands what's inside hints_p = MemoryPointer.new Socket::Foreign::AddrInfo.size # Socket::Foreign::AddrInfo is a struct addrinfo wrapper with friendly accessors hints = Socket::Foreign::AddrInfo.new hints_p hints[:ai_family] = family || 0 hints[:ai_socktype] = socktype || 0 hints[:ai_protocol] = protocol || 0 hints[:ai_flags] = flags || 0 # getaddrinfo(3) asks for a struct addrinfo **. # This creates a pointer to a pointer res_p = MemoryPointer.new :pointer # call out to C err = Socket::Foreign.getaddrinfo host, service, hints_p, res_p # check for errors raise SocketError, Socket::Foreign.gai_strerror(err) unless err == 0 # now we read out the pointer that getaddrinfo() passed us, and cast it # to a struct addrinfo * res = Socket::Foreign::AddrInfo.new res_p.read_pointer addrinfos =  loop do addrinfo =  # Extract data addrinfo << Socket::Constants::AF_TO_FAMILY[res[:ai_family]] ai_sockaddr = res[:ai_addr].read_string res[:ai_addrlen] sockaddr = Socket::Foreign::unpack_sa_ip ai_sockaddr, true addrinfo << sockaddr.pop # port addrinfo.concat sockaddr # hosts addrinfo << res[:ai_family] addrinfo << res[:ai_socktype] addrinfo << res[:ai_protocol] addrinfos << addrinfo # struct addrinfo is a linked list, so if we've hit the end, stop break unless res[:ai_next] # otherwise, down the linked-list res = Socket::Foreign::AddrInfo.new res[:ai_next] end return addrinfos ensure # like a C code, we have to free our MemoryPointer objects hints_p.free if hints_p if res_p then # also, we have to do any C-side cleanup Socket::Foreign.freeaddrinfo res_p.read_pointer res_p.free end end
getaddrinfo(3), freeaddrinfo(3) and gai_strerror(3) are wrapped up by FFI like this:
attach_function "gai_strerror", :gai_strerror, [:int], :string attach_function "getaddrinfo", :getaddrinfo, [:string, :string, :pointer, :pointer], :int attach_function "freeaddrinfo", :freeaddrinfo, [:pointer], :void
The first argument is the C function name, the second is the Ruby name, the third is the input arguments, and the fourth is the return type. Currently, FFI can only wrap up C functions with six or fewer args.
AddrInfo struct is wrapped up like this:
class AddrInfo < FFI::Struct config("rbx.platform.addrinfo", :ai_flags, :ai_family, :ai_socktype, :ai_protocol, :ai_addrlen, :ai_addr, :ai_canonname, :ai_next) end
The config method pulls pre-generated struct information out of a Rubinius config file and hooks up accessors to each of the struct’s fields. The accessors know which offset into the struct the data lives at and what type to convert data from and to when working with the struct. The information is collected at Rubinius build time by a small bit of C code.
I still have some confusion between passing an
Socket::Foreign::AddrInfo vs. passing a
MemoryPointer instance (which is what an FFI wrapped function understands) to an FFI-wrapped function, so we’re going to clean up that part of the API to make it more natural. Instead you’ll be able to initialize an
FFI::Struct directly and pass it to the FFI-wrapped function. This will make the code quite a bit cleaner.
Comments are disabled