rubygems.org guides 翻译四(Gems with Extensions)

目录

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 the MyMalloc 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命名的建议。

  1. ext/<name> 目录包含源代码和 extconf.rb
  2. ext/<name>/<name>.c 主函数源代码文件 (there may be others)
  3. ext/<name>/<name>.c contains a function Init_<name>. (The name following Init_ function must exactly match the name of the extension for it to be loadable by require.)
  4. ext/<name>/extconf.rb calls create_makefile('<name>/<name>') only when the all the pieces needed to compile the extension are present.
  5. The gemspec sets extensions = ['ext/<name>/extconf.rb'] and includes any of the necessary extension source files in the files list.
  6. lib/<name>.rb contains require '<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

你可能感兴趣的:(rubygems.org guides 翻译四(Gems with Extensions))