目录
1.gem布局
2.extconf.rb
3.C扩展
4.rake编译
5.gem说明
6.扩展命名规范
7.扩展阅读
前言:
许多gem使用C库作为扩展,使用ruby的接口来使用C代码。例如nokogiri这个gem包含了两个扩展库libxml2 and libxslt
pg(PostgreSQL的驱动)和mysql,mysql2(mysql的驱动)也使用了c扩展。
创建一个包含扩展的gem包含几个步骤,下面将关注此,是你轻松创建带扩展的gem。这个文档会使用到malloc和free两个c标准库。
一、gem布局
每一个gem都开始于一个包含任务的Rakefile文件,扩展的文件需要放在ext目录下,他是extention的缩写。在这个例子里面,我们会使用my_malloc作为名字。
一些扩展一部分是用C写的,一部分是用ruby写的。如果你要支持多语言扩展,例如C和java。你应该把ext目录下C文件对应的ruby文件也放入到lib目录下。
原文:If you are going to support multiple languages, such as C and Java extensions, you should put the C-specific ruby files under the ext/
directory as well in a lib/
directory.
Rakefile ext/my_malloc/extconf.rb # extension configuration ext/my_malloc/my_malloc.c # extension source lib/my_malloc.rb # generic features
当扩展编译ext/my_malloc/lib/下的
c文件后,就会安装到lib目录下。
二、extconf.rb
extconf.rb中是编译你自己的C扩展所需要的Makefile文件。extconf.rb会检查你的扩展所需要的函数,宏指令(macros)和共享库。如果缺少某一项,extconf.rb会立即退出。
下面这个extconf.rb会检查malloc和free,然后创建一个makefile,再将编译后的lib/my_malloc/my_malloc.so扩展按照。
require "mkmf" abort "missing malloc()" unless have_func "malloc" abort "missing free()" unless have_func "free" create_makefile "my_malloc/my_malloc"
mkmf documentation and README.EXT
查看更多关于创建extconf.rb以及其函数文档的细节。
三、C扩展
C扩展包含malloc和free两个函数到ext/my_malloc/my_malloc.c,如下:
#include <ruby.h> struct my_malloc { size_t size; void *ptr; }; static void my_malloc_free(void *p) { struct my_malloc *ptr = p; if (ptr->size > 0) free(ptr->ptr); } static VALUE my_malloc_alloc(VALUE klass) { VALUE obj; struct my_malloc *ptr; obj = Data_Make_Struct(klass, struct my_malloc, NULL, my_malloc_free, ptr); ptr->size = 0; ptr->ptr = NULL; return obj; } static VALUE my_malloc_init(VALUE self, VALUE size) { struct my_malloc *ptr; size_t requested = NUM2SIZET(size); if (0 == requested) rb_raise(rb_eArgError, "unable to allocate 0 bytes"); Data_Get_Struct(self, struct my_malloc, ptr); ptr->ptr = malloc(requested); if (NULL == ptr->ptr) rb_raise(rb_eNoMemError, "unable to allocate %ld bytes", requested); ptr->size = requested; return self; } static VALUE my_malloc_release(VALUE self) { struct my_malloc *ptr; Data_Get_Struct(self, struct my_malloc, ptr); if (0 == ptr->size) return self; ptr->size = 0; free(ptr->ptr); return self; } void Init_my_malloc(void) { VALUE cMyMalloc; cMyMalloc = rb_const_get(rb_cObject, rb_intern("MyMalloc")); rb_define_alloc_func(cMyMalloc, my_malloc_alloc); rb_define_method(cMyMalloc, "initialize", my_malloc_init, 1); rb_define_method(cMyMalloc, "free", my_malloc_release, 0); }
这个c扩展文件很简单,包含以下几部分。
-
struct my_malloc
to hold the allocated memory -
my_malloc_free()
to free the allocated memory after garbage collection -
my_malloc_alloc()
to create the ruby wrapper object -
my_malloc_init()
to allocate memory from ruby -
my_malloc_release()
to free memory from ruby -
Init_my_malloc()
to register the functions in theMyMalloc
class.
你可以像下面这样测试扩展:
$ cd ext/my_malloc
$ ruby extconf.rb
checking for malloc()... yes
checking for free()... yes
creating Makefile
$ make
compiling my_malloc.c
linking shared-object my_malloc.bundle
$ cd ../..
$ ruby -Ilib:ext -r my_malloc -e "p MyMalloc.new(5).free"
#<MyMalloc:0x007fed838addb0>
但这会跑好一会儿,我们使之自动化。
四、rake编译
rake-compiler 是一个负责编译扩展的任务集合。rake-compiler可以同时使用java或者C在同一个项目里面(nokogiri 就是这么干的),添加rake-compiler灰常简单。
require "rake/extensiontask" Rake::ExtensionTask.new "my_malloc" do |ext| ext.lib_dir = "lib/my_malloc" end现在你就可以使用rake compile编译这个扩展,而且可以hook这个编译任务到其他任务(例如test)
设置lib_dir参数以便放置lib/my_malloc/my_malloc.so(.bundle or .dll)的
扩展文件。
This allows the top-level file for the gem to be a ruby file. This allows you to write the parts that are best suited to ruby in ruby.(待翻译)
例如:
class MyMalloc VERSION = "1.0" end require "my_malloc/my_malloc"设置lib_dir也可以使你包含预编译的扩展,以便适应多个ruby版本。(ruby1.9.3的扩展不适用与ruby2.0.0),
lib/my_malloc.rb会选取合适的扩展来安装。
五、gem说明
构建gem的最后一步就是把extconf.rb注册到gemspec的extensions数组中。
Gem::Specification.new "my_malloc", "1.0" do |s| # [...] s.extensions = %w[ext/my_malloc/extconf.rb] end
现在你可以release这个gem了。
六、扩展命名
为了避免gem中无意识的交互(unintended interactions),最好的做法是每一个gem都放在一个单独的目录下。下面是关于gem命名的建议。
-
ext/<name>
目录包含源代码和extconf.rb
-
ext/<name>/<name>.c
主函数源代码文件 (there may be others) -
ext/<name>/<name>.c
contains a functionInit_<name>
. (The name followingInit_
function must exactly match the name of the extension for it to be loadable by require.) -
ext/<name>/extconf.rb
callscreate_makefile('<name>/<name>')
only when the all the pieces needed to compile the extension are present. - The gemspec sets
extensions = ['ext/<name>/extconf.rb']
and includes any of the necessary extension source files in thefiles
list. -
lib/<name>.rb
containsrequire '<name>/<name>'
which loads the C extension
七、扩展阅读
- my_malloc 包含该扩展的源码以及详细的注释
- README.EXT 详细描述了如何在ruby中构建一个gem
- MakeMakefile 包含 mkmf.rb 的文档, the library extconf.rb uses to detect ruby and C library features
- rake-compiler integrates building C and Java extensions into your Rakefile in a smooth manner.
- Writing C extensions part 1 and part 2) by Aaron Patterson
- Interfaces to C libraries can be written using ruby and fiddle (part of the standard library) or ruby-ffi