Ruby解析器的性能一向被人垢病,很多性能敏感的程序,都必须用C语言来编写,然后使用Ruby去调用C库。但糟糕的是,Ruby调用C库并不是一件轻松的活,需要你对Ruby的内部数据结构有比较深入的了解,甚至需要你仔细阅读Ruby相关的源代码,然后用C语言编程作为黏合剂,用Ruby特有的C API去给外部的C库增加一层封装。
以上的过程,相当于你要打开Ruby解析器的内部进行API调用了,对程序员的要求很高。而且一旦Ruby内部的数据结构随着版本升级发生变动,你的这些黏合剂程序就必须重写。比方说现在Ruby 1.9出来以后,绝大部分Ruby的C扩展库统统无法正常编译。
基于以上这些原因,给Ruby写C的扩展库一件非常痛苦的事情,而且也是Ruby社区程序员一直抱怨的问题之一。然而随着FFI的出现,我们即将告别这些痛苦的历史!
FFI即Foreign Function Interface,外部函数调用接口,并非Ruby独有的概念,只不过因为Ruby扩展库带来的痛苦,使得Ruby的FFI显得格外迫切。FFI最早已经在Rubinius虚拟机平台上实现了,随后在JRuby上面也得以实现,而今天:2008年11月1日,在Ruby官方版本的解析器Ruby 1.8.6/1.8.7和1.9版本上也可以使用FFI了。
安装FFI很简单:
使用FFI也很简单,比方说调用C语言标准库的printf函数,我们可以这样来写:
- require 'ffi'
- module MyExtModule
- extend FFI::Library
- attach_function :printf, [:string], :void
- end
-
- MyExtModule.printf("Hello FFI\n")
用attach_fuction就可以把C语言标准库的printf函数绑定到ruby里面来了,后面的参数分别声明C函数的传入参数和返回值。如果需要引入其他C库,可以预先使用ffi_lib指定库的名称。(我使用ffi_lib未能绑定非标准库,有可能是FFI的bug?也有可能是我没有设置对?)
有了FFI,在Ruby语言里面调用C库,就变成了一件异常轻松的事情,完全不需要ruby程序员再去吭哧吭哧啃C语言了,只要你手里捧着C库的API手册能看清楚函数的参数定义就够用了,然后你就可以直接在ruby里面随心所欲的调用它了,在调用之前,只需要用attach_function进行一次函数绑定声明即可。这对于整个ruby社区来说是一件非常棒的好消息。
在FFI的源代码树的samples目录下面还有几个小例子,大家可以自己看看:
http://kenai.com/projects/ruby-ffi/sources/723/show
作为一个对比,我们来看看传统的ruby扩展库是如何调用C库的吧。以上面的为例,如果我们希望在ruby里面使用C库的printf函数,那么要先用ruby API的格式来写一段C代码去封装它:
- #include <ruby.h>
-
- static VALUE
- dummy_printf(VALUE self, VALUE format, VALUE num)
- {
- int rlt = printf(RSTRING_PTR(format), NUM2INT(num));
- return INT2FIX(rlt);
- }
-
- void Init_printf()
- {
-
- rb_define_method(rb_mKernel, "dummy_printf",
- RUBY_METHOD_FUNC(dummy_printf), 2);
- }
当然你必须对ruby.h里面定义的ruby内部数据结构和ruby内部的函数调用有充分的了解。然后你要创建一个extconf.rb文件:
- require 'mkmf'
- create_makefile('printf')
运行这个文件:ruby extconf.rb,会创建出来编译这段C代码的Makefile,然后你再编译和安装,最终这个共享链接库会被拷贝到你的ruby的本地扩展库目录下面。最后你才可以在ruby里面调用这个封装好的dummy_printf方法。
另外,对于通过FFI去调用比较复杂的C库的时候,会涉及到不同语言之间的数据类型转换问题,关于数据类型的对照表,以及FFI使用更详细的介绍请看张驰原(他也是rmmseg的作者)的文章:
http://pluskid.lifegoo.com/?p=370