建立一個寶石,其中包含在安裝時建置的延伸功能。
許多寶石使用延伸功能來封裝以 C 編寫的函式庫,並使用 ruby 封裝器。範例包括 nokogiri,它封裝 libxml2 和 libxslt,pg,它是 PostgreSQL 資料庫 的介面,以及 mysql 和 mysql2 寶石,它們提供 MySQL 資料庫 的介面。
建立使用延伸功能的寶石涉及多個步驟。本指南將重點放在您應該放入寶石規格中,以使此過程盡可能簡單且易於維護的內容。本指南中的延伸功能將封裝 C 標準函式庫中的 malloc()
和 free()
。
寶石配置
每個寶石都應從一個 Rakefile 開始,其中包含開發人員處理寶石所需的工作。擴充套件的檔案應放入 ext/
目錄中,目錄名稱應與擴充套件名稱相符。在此範例中,我們將名稱設為「my_malloc」。
有些擴充套件將部分以 C 語言撰寫,部分以 Ruby 語言撰寫。如果您要支援多種語言,例如 C 和 Java 擴充套件,您應將特定於 C 的 Ruby 檔案也放入 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 必須傳回錯誤訊息。
以下是 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 文件 和 extension.rdoc,以取得有關建立 extconf.rb 和這些方法文件進一步的資訊。
C 擴充套件
包裝 malloc()
和 free()
的 C 擴充套件位於 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()
用於在垃圾回收後釋放已配置的記憶體my_malloc_alloc()
用於建立 Ruby 包裝物件my_malloc_init()
用於從 Ruby 配置記憶體my_malloc_release()
以釋放 ruby 的記憶體Init_my_malloc()
以註冊MyMalloc
類別中的函式。
現在,我們可以建立實際的 MyMalloc
類別,並在 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 任務,用於自動化擴充套件建立。rake-compiler 可與同一個專案中的 C 或 Java 擴充套件一起使用(nokogiri 以這種方式使用它)。
首先安裝 gem
$ gem install rake-compiler
將 rake-compiler 加入 Rakefile
非常簡單
require "rake/extensiontask"
Rake::ExtensionTask.new "my_malloc" do |ext|
ext.lib_dir = "lib/my_malloc"
end
現在,您可以使用 rake compile
建立擴充套件,並將編譯任務掛接到其他任務(例如測試)。
設定 lib_dir
會將共用程式庫置於 lib/my_malloc/my_malloc.so
(或 .bundle
或 .dll
)。這允許 gem 的頂層檔案成為 ruby 檔案。這讓您能夠以 ruby 編寫最適合 ruby 的部分。
例如
class MyMalloc
VERSION = "1.0"
end
require "my_malloc/my_malloc"
設定 lib_dir
也允許您建立一個 gem,其中包含針對多個版本的 ruby 預先建立的擴充套件。(Ruby 1.9.3 的擴充套件無法與 Ruby 2.0.0 的擴充套件一起使用)。lib/my_malloc.rb
可以選擇要安裝的正確共用程式庫。
Gem 規格
建立 gem 的最後一步驟是將 extconf.rb 加入 gemspec 中的擴充套件清單
Gem::Specification.new "my_malloc", "1.0" do |s|
# [...]
s.extensions = %w[ext/my_malloc/extconf.rb]
end
現在,您可以建立和釋出 gem!
擴充套件命名
為了避免 gem 之間意外的互動,建議每個 gem 將其所有檔案保留在單一目錄中。以下是名稱為 <name>
的 gem 的建議
ext/<name>
是包含原始檔和extconf.rb
的目錄ext/<name>/<name>.c
是主要的原始檔(可能還有其他檔案)ext/<name>/<name>.c
包含函式Init_<name>
。(Init_
之後的函式名稱必須與擴充套件的名稱完全相符,才能透過 require 載入。)ext/<name>/extconf.rb
僅在編譯擴充套件所需的所有部分都存在時,才會呼叫create_makefile('<name>/<name>')
。- gemspec 設定
extensions = ['ext/<name>/extconf.rb']
,並在files
清單中包含任何必要的擴充套件原始碼檔案。 lib/<name>.rb
包含require '<name>/<name>'
,用於載入 C 擴充套件
延伸閱讀
- my_malloc 包含此擴充套件的原始碼,並附有額外註解。
- extension.rdoc 詳細說明如何在 Ruby 中建置擴充套件
- MakeMakefile 包含 mkmf.rb 的文件,extconf.rb 函式庫用於偵測 Ruby 和 C 函式庫功能
- rake-compiler 以順暢的方式將建置 C 和 Java 擴充套件整合到 Rakefile 中。
- 撰寫 C 擴充套件第 1 部分 和 第 2 部分),作者:Aaron Patterson
- 可以使用 Ruby 和 fiddle(標準函式庫的一部分)或 ruby-ffi 來撰寫 C 函式庫介面
- 擴充 Ruby 是 Programming Ruby 書籍章節,說明如何建置 C 擴充套件。請注意:此內容較舊,且某些 C 擴充套件 API 已變更。