This post originated from an RSS feed registered with Ruby Buzz
by Eric Hodel.
Original Post: Rubinius' Foreign Function Interface
Feed Title: Segment7
Feed URL: http://blog.segment7.net/articles.rss
Feed Description: Posts about and around Ruby, MetaRuby, ruby2c, ZenTest and work at The Robot Co-op.
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:
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.
The 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 FFI::Struct like 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.