初探C extensions

近期,在做一个商场项目,需要用到图形验证码的功能。在网上找到了huacnlee/rucaptcha的gem基本达到了想要的想要实现的效果。
因为自己使用api的方式和文档介绍的ssr的使用方式略微不同,所以便对源码粗略的做了阅读。因为,这个gem的图片生成是采用c扩展的方式实现,所以我便对ruby如何进行c扩展产生了好奇。

rubygems上对c扩展做了比较条理性的介绍,这边搬运并翻译:

创建一个包含扩展的gem将在install的时候被构建:
很多gem扩展采用ruby包装c语言实现。就像nokogiri使用了libxml2和libxslt和, pg实现了postgres的接口和mysql2实现了mysql的接口.
创建一个使用扩展的gem,包含了几个步骤。本指南将重点介绍您应该在gem specification 中添加的内容,以使其尽可能简单和可维护。本指南中的扩展将从C标准库中包装malloc()和free()。

GEM LAYOUT

任何gem都需要一个包含tasks的Rakefile用于developers的工作。扩展文件需要摆放在 ext/文件下下匹配上扩展的名字。在这个例子里,我们将使用"my_malloc"为名字。
一些扩展部分使用c实现扩展以及部分使用ruby。如果你为了支持这种多语言,例如c和java扩展,你应该将C-specific文件 放在 ext/ 文件夹 以及 lib/ 文件夹下。

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/将会被安装在 lib/目录下

EXTCONF.RB

extconf.rb配置的Makefile将会用于构建你的扩展。extconf.rb必须检查扩展所依赖的必要函数,宏和共享库.当一些东西丢失时,extconf.rb必须报错退出.
这是一个exconf.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 文档以及 extension.doc查看关于创建extconf.rb的信息和有关的方法。

C EXTENSION

c扩展包含了malloc()和free()在ext/my_malloc/my_malloc.c如下:

#include 

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);
}

这个扩展包含了一些简单的部分:
struct my_malloc 用于支持分配内存
my_malloc_free() 释放内存在gc后
my_malloc_alloc() 创建ruby包裹的对象
my_alloc_init() 从ruby分配内存
my_malloc_release()从ruby释放内存
Init_my_malloc() 用于注册MyMalloc类中的函数
现在我们可以创建实现 MyMalloc class以及新定义的方法在ruby(lib/my_malloc.rb是正确的位置)

class MyMalloc
  VERSION = "1.0"
end

require "my_malloc/my_malloc"

你可以测试构建扩展如下:

$ 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"
#

但经过一段时间后,这将变得乏味。让它自动化吧!

RAKE-COMPILER

rake-compiler 设置了rake tasks用于自动化扩展构建。rake-compiler 可以用于c或java扩展在相同的项目里(nokogiri就是用这种方式)。
首先安装gem:
gem install rake-complier
添加rake-compiler到Rakefile是非常简单的:

require "rake/extensiontask"

Rake::ExtensionTask.new "my_malloc" do |ext|
  ext.lib_dir = "lib/my_malloc"
end

现在你可以使用 rake compiler构建扩展以及把编译任务挂在到其他任务里。
设置lib_dir位置分享库在 lib/my_malloc/my_malloc.so(or .bundle or .dll)。这使gem的顶层文件是一个ruby文件。这允许你使用ruby写更适合ruby的部分。
如下:

class MyMalloc

  VERSION = "1.0"

end

require "my_malloc/my_malloc"

设置 lib_dir 允许你构建gem包含预构建扩展给多个ruby版本。(一个用于ruby1.9.3的 将不能被用于ruby2.0.0)。lib/my_malloc.rb可以选择正确的共享库安装。

GEM SPECIFICATION

最后的构建gem的步骤就是把extconf.rb扩展列表加入到gemrspec;

Gem::Specification.new "my_malloc", "1.0" do |s|
  # [...]

  s.extensions = %w[ext/my_malloc/extconf.rb]
end

然后你可以构建发布gem!

EXTENSION NAMING

为了避免gem之间无意义的交互,将gem的所有文件放到单个文件夹将是个不错的主意。这里是一些gem名字的不错的建议:
1.ext / 是包含源文件和extconf.rb的目录
2.ext / / .c是主要的源文件(可能还有其他文件)
3.ext / / .c包含一个函数Init_ 。 (Init_函数后面的名称必须与扩展名称完全匹配才能由require加载。)
4.ext / /econfconf.rb仅在存在编译扩展所需的所有部分时才调用create_makefile(' / ')。
5.gemspec设置extensions = ['ext / /extconf.rb']并包含文件列表中的任何必要的扩展源文件。
6.lib / .rb包含require' / ',它加载C扩展名

进一步的阅读

my_malloc包含此扩展的源代码以及一些其他注释。
extension.rdoc更详细地描述了如何在ruby中构建扩展
MakeMakefile包含mkmf.rb的文档,extconf.rb库用于检测ruby和C库特性
rake-compiler以平滑的方式将构建C和Java扩展集成到Rakefile中。
Writing C extensions part 1 和 part 2分由Aaron Patterson编写
可以使用ruby和fiddle(标准库的一部分)或ruby-ffi编写与C库的接口
Extending Ruby是一本关于构建C扩展的Programming Ruby
小说。请注意:此内容有点旧,有些C扩展API已更改。

你可能感兴趣的:(初探C extensions)