初探C extensions

近期,在做一個商場項目,需要用到圖形驗證碼的功能。在網上找到了huacnlee/rucaptcha的gem基本達到了想要的想要實現的效果。
因為自己使用api的方式和文檔介紹的ssr的使用方式略微不同,所以便對源碼粗略的做了閱讀。因為,這個gem的圖片生成是采用c擴展的方式實現,所以我便對ruby如何進行c擴展產生了好奇。

rubygems上對c擴展做了比較條理性的介紹,這邊搬運并翻譯:

創(chuàng)建一個包含擴展的gem將在install的時候被構建:
很多gem擴展采用ruby包裝c語言實現。就像nokogiri使用了libxml2和libxslt和, pg實現了postgres的接口和mysql2實現了mysql的接口.
創(chuàng)建一個使用擴展的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()以及創(chuàng)建了一個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查看關于創(chuàng)建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() 創(chuàng)建ruby包裹的對象
my_alloc_init() 從ruby分配內存
my_malloc_release()從ruby釋放內存
Init_my_malloc() 用于注冊MyMalloc類中的函數
現在我們可以創(chuàng)建實現 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

然后你可以構建發(fā)布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 1part 2分由Aaron Patterson編寫
可以使用ruby和fiddle(標準庫的一部分)或ruby-ffi編寫與C庫的接口
Extending Ruby是一本關于構建C擴展的Programming Ruby
小說。請注意:此內容有點舊,有些C擴展API已更改。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容