近期,在做一个商场项目,需要用到图形验证码的功能。在网上找到了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 <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);
}
这个扩展包含了一些简单的部分:
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"
#<MyMalloc:0x007fed838addb0>
但经过一段时间后,这将变得乏味。让它自动化吧!
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 / <name>是包含源文件和extconf.rb的目录
2.ext / <name> / <name> .c是主要的源文件(可能还有其他文件)
3.ext / <name> / <name> .c包含一个函数Init_ <name>。 (Init_函数后面的名称必须与扩展名称完全匹配才能由require加载。)
4.ext / <name> /econfconf.rb仅在存在编译扩展所需的所有部分时才调用create_makefile('<name> / <name>')。
5.gemspec设置extensions = ['ext / <name> /extconf.rb']并包含文件列表中的任何必要的扩展源文件。
6.lib / <name> .rb包含require'<name> / <name>',它加载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已更改。
网友评论