為 Ruby 建立擴充函式庫

此文件說明如何為 Ruby 建立擴充函式庫。

基本知識

在 C 中,變數有型別,但資料沒有型別。相反地,Ruby 變數沒有靜態型別,但資料本身有型別,因此資料需要在語言之間轉換。

Ruby 中的物件由 C 型別「VALUE」表示。每個 VALUE 資料都有其資料型別。

若要從 VALUE 擷取 C 資料,您需要

  1. 識別 VALUE 的資料型別

  2. 將 VALUE 轉換為 C 資料

轉換為錯誤的資料型別可能會造成嚴重問題。

Ruby 資料型別

Ruby 詮釋器有下列資料型別

T_NIL

nil

T_OBJECT

一般物件

T_CLASS

類別

T_MODULE

模組

T_FLOAT

浮點數

T_STRING

字串

T_REGEXP

正規表示式

T_ARRAY

陣列

T_HASH

關聯陣列

T_STRUCT

(Ruby) 結構

T_BIGNUM

多精度整數

T_FIXNUM

Fixnum(31 位元或 63 位元整數)

T_COMPLEX

複數

T_RATIONAL

有理數

T_FILE

IO

T_TRUE

T_FALSE

T_DATA

資料

T_SYMBOL

符號

此外,還有數種其他類型在內部使用

T_ICLASS

包含的模組

T_MATCH

MatchData 物件

T_UNDEF

未定義

T_NODE

語法樹節點

T_ZOMBIE

等待完成的物件

大部分的類型都由 C 結構表示。

檢查 VALUE 資料的類型

ruby.h 中定義的巨集 TYPE() 顯示 VALUE 的資料類型。TYPE() 傳回上面所述的常數 T_XXXX。若要處理資料類型,您的程式碼看起來會像這樣

switch (TYPE(obj)) {
  case T_FIXNUM:
    /* process Fixnum */
    break;
  case T_STRING:
    /* process String */
    break;
  case T_ARRAY:
    /* process Array */
    break;
  default:
    /* raise exception */
    rb_raise(rb_eTypeError, "not valid value");
    break;
}

有資料類型檢查函式

void Check_Type(VALUE value, int type)

如果 VALUE 沒有指定類型,它會引發例外狀況。

還有針對 fixnum 和 nil 的較快檢查巨集。

FIXNUM_P(obj)
NIL_P(obj)

將 VALUE 轉換為 C 資料

T_NIL、T_FALSE、T_TRUE 的資料分別為 nil、false、true。它們是資料類型的單例。等效的 C 常數為:Qnil、Qfalse、Qtrue。如果 VALUE 不是 Qfalse 也不是 Qnil,RTEST() 會傳回 true。如果您需要區分 Qfalse 和 Qnil,請特別測試 Qfalse。

T_FIXNUM 資料是 31 位元或 63 位元長度的固定整數。此大小取決於 long 的大小:如果 long 是 32 位元,則 T_FIXNUM 是 31 位元,如果 long 是 64 位元,則 T_FIXNUM 是 63 位元。T_FIXNUM 可以使用 FIX2INT() 巨集或 FIX2LONG() 轉換為 C 整數。雖然您必須在使用它們之前檢查資料是否真的是 FIXNUM,但它們比較快。FIX2LONG() 絕不會引發例外狀況,但如果結果大於或小於 int 的大小,FIX2INT() 會引發 RangeError。還有 NUM2INT() 和 NUM2LONG(),它們會將任何 Ruby 數字轉換為 C 整數。這些巨集包含類型檢查,因此如果轉換失敗,會引發例外狀況。NUM2DBL() 可用於以相同方式擷取雙精度浮點值。

您可以使用巨集 StringValue() 和 StringValuePtr() 從 VALUE 取得 char*。StringValue(var) 會以 “var.to_str()” 的結果取代 var 的值。StringValuePtr(var) 執行相同的取代,並傳回 var 的 char* 表示形式。如果 var 是 String,這些巨集會略過取代。請注意,巨集只將 lvalue 作為它們的引數,以就地變更 var 的值。

您也可以使用名為 StringValueCStr() 的巨集。這就像 StringValuePtr(),但會在結果的結尾永遠加上一個 NUL 字元。如果結果包含 NUL 字元,這個巨集會導致 ArgumentError 例外。StringValuePtr() 不保證結果結尾有 NUL,而且結果可能包含 NUL。

其他資料類型有對應的 C 結構,例如 T_ARRAY 的 struct RArray 等。具有對應結構的類型的 VALUE 可以轉型為指標,以擷取結構的指標。轉型巨集會是每個資料類型的 RXXXX 形式;例如 RARRAY(obj)。請參閱「ruby.h」。不過,我們不建議直接存取 RXXXX 資料,因為這些資料結構很複雜。請使用對應的 rb_xxx() 函式存取內部結構。例如,要存取陣列的項目,請使用 rb_ary_entry(ary, offset) 和 rb_ary_store(ary, offset, obj)。

有一些結構成員的存取巨集,例如「RSTRING_LEN(str)」用於取得 Ruby String 物件的大小。已配置的區域可以用「RSTRING_PTR(str)」存取。

注意:除非您負責結果,否則請勿直接變更結構的值。這最後會導致有趣的錯誤。

將 C 資料轉換為 VALUE

將 C 資料轉換為 Ruby 值

FIXNUM

左移 1 位元,並開啟其最低有效位元 (LSB)。

其他指標值

轉型為 VALUE。

您可以透過檢查 LSB 來判斷 VALUE 是否為指標。

注意:Ruby 不允許任意指標值為 VALUE。它們應該是 Ruby 已知的結構的指標。已知的結構定義在 <ruby.h> 中。

若要將 C 數字轉換為 Ruby 值,請使用這些巨集

INT2FIX()

用於 31 位元內的整數。

INT2NUM()

用於任意大小的整數。

INT2NUM() 會將整數轉換為 Bignum(如果它超出 FIXNUM 範圍),但速度稍慢。

處理 Ruby 物件

正如我已經提到的,不建議修改物件的內部結構。若要處理物件,請使用 Ruby 解譯器提供的函式。以下列出一些(並非全部)有用的函式

String 函式

rb_str_new(const char *ptr, long len)

建立一個新的 Ruby 字串。

rb_str_new2(const char *ptr)
rb_str_new_cstr(const char *ptr)

從 C 字串建立一個新的 Ruby 字串。這等同於 rb_str_new(ptr, strlen(ptr))。

rb_str_new_literal(const char *ptr)

從 C 字串字面值建立一個新的 Ruby 字串。

rb_sprintf(const char *format, …)
rb_vsprintf(const char *format, va_list ap)

使用 printf(3) 格式建立一個新的 Ruby 字串。

注意:在格式字串中,「%」PRIsVALUE 可用於 Object#to_s(或如果設定「+」旗標,則為 Object#inspect)輸出(且相關引數必須為 VALUE)。由於它與「%i」衝突,對於格式字串中的整數,請使用「%d」。

rb_str_append(VALUE str1, VALUE str2)

將 Ruby 字串 str2 附加至 Ruby 字串 str1。

rb_str_cat(VALUE str, const char *ptr, long len)

將 ptr 中的 len 位元組資料附加至 Ruby 字串。

rb_str_cat2(VALUE str, const char* ptr)
rb_str_cat_cstr(VALUE str, const char* ptr)

將 C 字串 ptr 附加至 Ruby 字串 str。此函式等同於 rb_str_cat(str, ptr, strlen(ptr))。

rb_str_catf(VALUE str, const char* format, …)
rb_str_vcatf(VALUE str, const char* format, va_list ap)

根據類似 printf 的格式,將 C 字串格式和後續引數附加至 Ruby 字串 str。這些函式分別等同於 rb_str_append(str, rb_sprintf(format, …)) 和 rb_str_append(str, rb_vsprintf(format, ap))。

rb_enc_str_new(const char *ptr, long len, rb_encoding *enc)
rb_enc_str_new_cstr(const char *ptr, rb_encoding *enc)

建立一個具有指定編碼的新 Ruby 字串。

rb_enc_str_new_literal(const char *ptr, rb_encoding *enc)

從具有指定編碼的 C 字串字面值建立一個新的 Ruby 字串。

rb_usascii_str_new(const char *ptr, long len)
rb_usascii_str_new_cstr(const char *ptr)

建立一個具有編碼 US-ASCII 的新 Ruby 字串。

rb_usascii_str_new_literal(const char *ptr)

從具有編碼 US-ASCII 的 C 字串字面值建立一個新的 Ruby 字串。

rb_utf8_str_new(const char *ptr, long len)
rb_utf8_str_new_cstr(const char *ptr)

建立一個具有編碼 UTF-8 的新 Ruby 字串。

rb_utf8_str_new_literal(const char *ptr)

從具有編碼 UTF-8 的 C 字串字面值建立一個新的 Ruby 字串。

rb_str_resize(VALUE str, long len)

將 Ruby 字串調整為 len 位元組。如果 str 不可修改,此函式會引發例外。str 的長度必須事先設定。如果 len 小於舊長度,len 位元組以外的內容會被捨棄;否則,如果 len 大於舊長度,舊長度位元組以外的內容不會被保留,而是會變成垃圾。請注意,呼叫此函式可能會變更 RSTRING_PTR(str)。

rb_str_set_len(VALUE str, long len)

設定 Ruby 字串的長度。如果 str 不可修改,此函式會引發例外。此函式會保留長達 len 位元組的內容,而不論 RSTRING_LEN(str)。len 不得超過 str 的容量。

rb_str_modify(VALUE str)

準備一個 Ruby 字串進行修改。如果 str 無法修改,此函式會引發例外,或者如果 str 的緩衝區是共用的,此函式會配置新的緩衝區,使其不再共用。在使用 RSTRING_PTR 和/或 rb_str_set_len 修改內容之前,您必須永遠呼叫此函式。

陣列 函式

rb_ary_new()

建立一個沒有元素的陣列。

rb_ary_new2(long len)
rb_ary_new_capa(long len)

建立一個沒有元素的陣列,配置 len 個元素的內部緩衝區。

rb_ary_new3(long n, …)
rb_ary_new_from_args(long n, …)

從參數建立一個 n 元素的陣列。

rb_ary_new4(long n, VALUE *elts)
rb_ary_new_from_values(long n, VALUE *elts)

從 C 陣列建立一個 n 元素的陣列。

rb_ary_to_ary(VALUE obj)

將物件轉換為陣列。等同於 Object#to_ary。

有許多函式可操作陣列。如果給定其他類型,它們可能會傾印核心。

rb_ary_aref(int argc, const VALUE *argv, VALUE ary)

等同於 Array#[]

rb_ary_entry(VALUE ary, long offset)

ary[offset]

rb_ary_store(VALUE ary, long offset, VALUE obj)

ary[offset] = obj

rb_ary_subseq(VALUE ary, long beg, long len)

ary[beg, len]

rb_ary_push(VALUE ary, VALUE val)
rb_ary_pop(VALUE ary)
rb_ary_shift(VALUE ary)
rb_ary_unshift(VALUE ary, VALUE val)

ary.push, ary.pop, ary.shift, ary.unshift

rb_ary_cat(VALUE ary, const VALUE *ptr, long len)

將 ptr 中 len 個物件元素附加到陣列。

使用 C 擴充 Ruby

新增新功能至 Ruby

您可以新增新功能(類別、方法等)至 Ruby 詮釋器。Ruby 提供 API 來定義下列項目

類別模組 定義

若要定義類別或模組,請使用下列函式

VALUE rb_define_class(const char *name, VALUE super)
VALUE rb_define_module(const char *name)

這些函式會傳回新建立的類別或模組。您可能想要將此參考儲存到變數中,以便稍後使用。

若要定義巢狀類別或模組,請使用下列函式

VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
VALUE rb_define_module_under(VALUE outer, const char *name)

方法 和單例方法定義

若要定義方法或單例方法,請使用這些函數

void rb_define_method(VALUE klass, const char *name,
                      VALUE (*func)(ANYARGS), int argc)

void rb_define_singleton_method(VALUE object, const char *name,
                                VALUE (*func)(ANYARGS), int argc)

「argc」代表 C 函數的引數數量,必須小於 17。不過我懷疑你會需要這麼多。

如果「argc」為負數,它指定呼叫順序,而不是引數數量。

如果 argc 為 -1,函數將呼叫為

VALUE func(int argc, VALUE *argv, VALUE obj)

其中 argc 為實際引數數量,argv 為引數的 C 陣列,而 obj 為接收器。

如果 argc 為 -2,引數會傳遞在 Ruby 陣列中。函數將呼叫為

VALUE func(VALUE obj, VALUE args)

其中 obj 為接收器,而 args 為包含實際引數的 Ruby 陣列。

還有更多函數可定義方法。其中一個函數會將 ID 作為要定義的方法名稱。另請參閱下方的 ID 或 Symbol

void rb_define_method_id(VALUE klass, ID name,
                         VALUE (*func)(ANYARGS), int argc)

有兩個函數可定義私有/受保護的方法

void rb_define_private_method(VALUE klass, const char *name,
                              VALUE (*func)(ANYARGS), int argc)
void rb_define_protected_method(VALUE klass, const char *name,
                                VALUE (*func)(ANYARGS), int argc)

最後,rb_define_module_function 定義模組函數,它們是模組的私有且單例的方法。例如,sqrt 是在 Math 模組中定義的模組函數。它可以用下列方式呼叫

Math.sqrt(4)

include Math
sqrt(4)

若要定義模組函數,請使用

void rb_define_module_function(VALUE module, const char *name,
                               VALUE (*func)(ANYARGS), int argc)

此外,函數式方法(在 Kernel 模組中定義的私有方法)可以使用下列方式定義

void rb_define_global_function(const char *name, VALUE (*func)(ANYARGS), int argc)

若要定義方法的別名,

void rb_define_alias(VALUE module, const char* new, const char* old);

若要定義屬性的讀取器/寫入器,

void rb_define_attr(VALUE klass, const char *name, int read, int write)

若要定義和取消定義「配置」類別方法,

void rb_define_alloc_func(VALUE klass, VALUE (*func)(VALUE klass));
void rb_undef_alloc_func(VALUE klass);

func 必須將 klass 作為引數,並傳回新配置的執行個體。此執行個體應盡可能為空,且不包含任何昂貴的(包括外部)資源。

如果你正在覆寫類別任何祖先的現有方法,你可以依賴

VALUE rb_call_super(int argc, const VALUE *argv)

若要指定在呼叫 super 時是否傳遞關鍵字引數

VALUE rb_call_super_kw(int argc, const VALUE *argv, int kw_splat)

kw_splat 可以有下列可能值(由所有接受 kw_splat 引數的方法使用)

RB_NO_KEYWORDS

不要傳遞關鍵字

RB_PASS_KEYWORDS

傳遞關鍵字,最後一個引數應為關鍵字的雜湊

RB_PASS_CALLED_KEYWORDS

如果使用關鍵字呼叫目前方法,則傳遞關鍵字,對於引數委派很有用

若要取得目前範圍的接收器(如果沒有其他可用方式),你可以使用

VALUE rb_current_receiver(void)

常數定義

我們有 2 個函式來定義常數

void rb_define_const(VALUE klass, const char *name, VALUE val)
void rb_define_global_const(const char *name, VALUE val)

前者是在指定的類別/模組下定義常數。後者是定義全域常數。

從 C 使用 Ruby 功能

有幾種方法可以從 C 程式碼呼叫 Ruby 的功能。

在字串中評估 Ruby 程式

從 C 程式使用 Ruby 功能最簡單的方法是將字串評估為 Ruby 程式。此函式將執行此工作

VALUE rb_eval_string(const char *str)

評估是在目前的內容下完成,因此可以存取內層方法(由 Ruby 定義)目前的區域變數。

請注意評估可能會引發例外。有一個更安全的函式

VALUE rb_eval_string_protect(const char *str, int *state)

如果發生錯誤,它會傳回 nil。此外,如果 str 已成功評估,*state 會為零,否則為非零。

ID 或 Symbol

您可以直接呼叫方法,而無需剖析字串。首先我需要說明 ID。ID 是表示 Ruby 識別碼(例如變數名稱)的整數。對應於 ID 的 Ruby 資料類型是 Symbol。它可以從 Ruby 中以以下形式存取

:Identifier

:"any kind of string"

您可以使用以下方式從 C 程式碼中的字串取得 ID 值

rb_intern(const char *name)
rb_intern_str(VALUE name)

您可以使用以下方式從作為引數提供的 Ruby 物件 (SymbolString) 擷取 ID

rb_to_id(VALUE symbol)
rb_check_id(volatile VALUE *name)
rb_check_id_cstr(const char *name, long len, rb_encoding *enc)

如果引數不是 SymbolString,這些函式會嘗試將引數轉換為 String。第二個函式會將轉換後的結果儲存在 *name 中,如果字串不是已知的符號,則傳回 0。此函式傳回非零值後,*name 永遠是 SymbolString,否則,如果結果為 0,則為 String。第三個函式採用以 NUL 結束的 C 字串,而不是 Ruby VALUE。

您可以使用以下方式從作為引數提供的 Ruby 物件 (SymbolString) 擷取 Symbol

rb_to_symbol(VALUE name)
rb_check_symbol(volatile VALUE *namep)
rb_check_symbol_cstr(const char *ptr, long len, rb_encoding *enc)

這些函式與上述函式類似,但它們傳回 Symbol,而不是 ID。

您可以使用以下方式將 C ID 轉換為 Ruby Symbol

VALUE ID2SYM(ID id)

並使用以下方式將 Ruby Symbol 物件轉換為 ID

ID SYM2ID(VALUE symbol)

從 C 呼叫 Ruby 方法

若要直接呼叫方法,可以使用以下函式

VALUE rb_funcall(VALUE recv, ID mid, int argc, ...)

此函式會呼叫接收方上的方法,方法名稱由符號 mid 指定。

存取變數和常數

你可以使用存取函式來存取類別變數和實例變數。此外,全域變數可以在兩個環境中共享。無法存取 Ruby 的區域變數。

以下是存取/修改實例變數的函式

VALUE rb_ivar_get(VALUE obj, ID id)
VALUE rb_ivar_set(VALUE obj, ID id, VALUE val)

id 必須是符號,可以用 rb_intern() 擷取。

若要存取類別/模組的常數

VALUE rb_const_get(VALUE obj, ID id)

請參閱上方的常數定義。

Ruby 和 C 之間的資訊共享

可以從 C 存取的 Ruby 常數

如第 1.3 節所述,下列 Ruby 常數可以從 C 參考。

Qtrue
Qfalse

布林值。Qfalse 在 C 中也是 false(即 0)。

Qnil

C 範圍內的 Ruby nil。

C 和 Ruby 共享的全域變數

可以使用共享全域變數在兩個環境之間共享資訊。若要定義它們,可以使用下列函式

void rb_define_variable(const char *name, VALUE *var)

此函式定義兩個環境共享的變數。可以透過 Ruby 名為「name」的全域變數存取「var」指向的全域變數值。

可以使用下列函式定義唯讀(當然是從 Ruby)變數。

void rb_define_readonly_variable(const char *name, VALUE *var)

你可以定義掛鉤變數。存取掛鉤變數時會呼叫存取器函式(getter 和 setter)。

void rb_define_hooked_variable(const char *name, VALUE *var,
                               VALUE (*getter)(), void (*setter)())

如果你需要提供 setter 或 getter,請為不需要的掛鉤提供 0。如果兩個掛鉤都是 0,rb_define_hooked_variable() 的作用就像 rb_define_variable()。

getter 和 setter 函式的原型如下

VALUE (*getter)(ID id, VALUE *var);
void (*setter)(VALUE val, ID id, VALUE *var);

你也可以定義沒有對應 C 變數的 Ruby 全域變數。變數的值只會由掛鉤設定/取得。

void rb_define_virtual_variable(const char *name,
                                VALUE (*getter)(), void (*setter)())

getter 和 setter 函式的原型如下

VALUE (*getter)(ID id);
void (*setter)(VALUE val, ID id);

將 C 資料封裝到 Ruby 物件中

有時你需要在 C 世界中將結構公開為 Ruby 物件。在這種情況下,利用 TypedData_XXX 巨集系列,可以相互轉換結構指標和 Ruby 物件。

C 結構轉換為 Ruby 物件

你可以使用下一個巨集將 sval(指向結構的指標)轉換為 Ruby 物件。

TypedData_Wrap_Struct(klass, data_type, sval)

TypedData_Wrap_Struct() 會傳回建立的 Ruby 物件,型別為 VALUE。

klass 引數是物件的類別。klass 應該衍生自 rb_cObject,且必須透過呼叫 rb_define_alloc_func 或 rb_undef_alloc_func 來設定配置器。

data_type 是指向 const rb_data_type_t 的指標,用於描述 Ruby 應如何管理結構。

rb_data_type_t 定義如下。我們來看看結構的每個成員。

typedef struct rb_data_type_struct rb_data_type_t;

struct rb_data_type_struct {
    const char *wrap_struct_name;
    struct {
        void (*dmark)(void*);
        void (*dfree)(void*);
        size_t (*dsize)(const void *);
        void (*dcompact)(void*);
        void *reserved[1];
    } function;
    const rb_data_type_t *parent;
    void *data;
    VALUE flags;
};

wrap_struct_name 是此結構實例的識別碼。它基本上用於收集和發出統計資料。因此,識別碼在程序中必須是唯一的,但不需要有效作為 C 或 Ruby 識別碼。

這些 dmark / dfree 函式會在執行 GC 時呼叫。在執行期間不允許配置任何物件,因此請勿在其中配置 Ruby 物件。

dmark 是用於標記從結構參照的 Ruby 物件的函式。如果結構保留此類參照,則必須使用 rb_gc_mark 或其系列標記結構中的所有參照。

dfree 是用於釋放指標配置的函式。如果是 RUBY_DEFAULT_FREE,指標將只會被釋放。

dsize 計算結構消耗的記憶體(以位元組為單位)。其參數是指向結構的指標。如果難以實作此類函式,你可以將 0 傳遞為 dsize。但仍建議避免使用 0。

當執行記憶體壓縮時,會呼叫 dcompact。在此,可以根據 rb_gc_location() 更新已由 rb_gc_mark_movable() 標記的參照 Ruby 物件。

你必須將 reserved 填入 0。

parent 可以指向 Ruby 物件繼承的另一個 C 類型定義。然後,TypedData_Get_Struct() 也會接受衍生物件。

你可以使用任意值填入「data」供你使用。Ruby 不會對成員採取任何動作。

flags 是下列旗標值的按位元 OR。由於它們需要深入瞭解 Ruby 中的垃圾回收器,因此如果你不確定,你可以將 flags 設定為 0。

RUBY_TYPED_FREE_IMMEDIATELY

此旗標會在 GC 需要釋放結構時,立即呼叫 dfree()。如果你確信 dfree 永遠不會解除 Ruby 的內部鎖定 (GVL),則可以指定此旗標。

如果未設定此旗標,Ruby 會延後呼叫 dfree(),並在與完成處理程序相同的時間點呼叫 dfree()。

RUBY_TYPED_WB_PROTECTED

表示物件實作支援寫入屏障。如果設定此旗標,Ruby 可以更有效率地對物件執行垃圾回收。

不過,設定此旗標後,你必須負責在所有物件方法實作中適當地加入寫入屏障。否則,Ruby 在執行時可能會當機。

有關寫入屏障的詳細資訊,請參閱附錄 D 中的「世代 GC」。

RUBY_TYPED_FROZEN_SHAREABLE

此旗標表示如果物件已凍結,則該物件為可共用物件。有關詳細資訊,請參閱附錄 F。

如果未設定此旗標,則物件無法透過 Ractor.make_shareable() 方法成為可共用物件。

你可以一步驟配置並包裝結構。

TypedData_Make_Struct(klass, type, data_type, sval)

此巨集會傳回已配置的 T_DATA 物件,包裝已配置的結構指標。此巨集的作用類似於

(sval = ZALLOC(type), TypedData_Wrap_Struct(klass, data_type, sval))

引數 klass 和 data_type 的作用類似於 TypedData_Wrap_Struct() 中的對應引數。已配置結構的指標會指定給 sval,而 sval 應為指定型別的指標。

宣告式標記/壓縮結構參考

如果結構參考的 Ruby 物件是單純值,未包裝在條件式邏輯或複雜資料結構中,則提供標記和參考更新的替代方法,方法是宣告結構中 VALUE 的偏移參考。

這樣做可讓 Ruby GC 支援標記這些參考,並讓 GC 壓縮,而無需定義 dmarkdcompact 回呼。

你必須定義一個靜態 VALUE 指標清單,指向結構中存放參考的偏移,並將「data」成員設定為指向此參考清單。參考清單必須以 RUBY_END_REFS 結尾。

已提供一些巨集,以簡化邊緣參考

以下範例來自 Dir (定義於 dir.c)

// The struct being wrapped. Notice this contains 3 members of which the second
// is a VALUE reference to another ruby object.
struct dir_data {
    DIR *dir;
    const VALUE path;
    rb_encoding *enc;
}

// Define a reference list `dir_refs` containing a single entry to `path`.
// Needs terminating with RUBY_REF_END
RUBY_REFERENCES(dir_refs) = {
    RUBY_REF_EDGE(dir_data, path),
    RUBY_REF_END
};

// Override the "dmark" field with the defined reference list now that we
// no longer need a marking callback and add RUBY_TYPED_DECL_MARKING to the
// flags field
static const rb_data_type_t dir_data_type = {
    "dir",
    {RUBY_REFS_LIST_PTR(dir_refs), dir_free, dir_memsize,},
    0, NULL, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_DECL_MARKING
};

以這種方式宣告簡單的參考,可讓 GC 標記和移動底層物件,並在壓縮期間自動更新對其的參考。

Ruby 物件至 C 結構

若要從 T_DATA 物件擷取 C 指標,請使用巨集 TypedData_Get_Struct()。

TypedData_Get_Struct(obj, type, &data_type, sval)

結構的指標會指定給變數 sval。

有關詳細資訊,請參閱以下範例。

範例 - 建立 dbm 擴充套件

以下是建立擴充套件函式庫的範例。這是用於存取 DBM 的擴充套件。完整的來源包含在 Ruby 來源樹狀結構的 ext/ 目錄中。

建立目錄

% mkdir ext/dbm

在 ext 目錄下為擴充套件函式庫建立目錄。

設計函式庫

在建立函式庫之前,您需要設計函式庫功能。

撰寫 C 程式碼

您需要為擴充套件函式庫撰寫 C 程式碼。如果您的函式庫只有一個來源檔案,建議選擇「LIBRARY.c」作為檔案名稱。另一方面,如果您的函式庫有多個來源檔案,請避免選擇「LIBRARY.c」作為檔案名稱。它可能會與某些平台上的中間檔案「LIBRARY.o」衝突。請注意,下方說明的 mkmf 函式庫中的一些函式會產生檔案「conftest.c」以進行編譯檢查。您不應選擇「conftest.c」作為來源檔案的名稱。

Ruby 會執行函式庫中名為「Init_LIBRARY」的初始化函式。例如,載入函式庫時會執行「Init_dbm()」。

以下是初始化函式的範例。

#include <ruby.h>
void
Init_dbm(void)
{
    /* define DBM class */
    VALUE cDBM = rb_define_class("DBM", rb_cObject);
    /* Redefine DBM.allocate
    rb_define_alloc_func(cDBM, fdbm_alloc);
    /* DBM includes Enumerable module */
    rb_include_module(cDBM, rb_mEnumerable);

    /* DBM has class method open(): arguments are received as C array */
    rb_define_singleton_method(cDBM, "open", fdbm_s_open, -1);

    /* DBM instance method close(): no args */
    rb_define_method(cDBM, "close", fdbm_close, 0);
    /* DBM instance method []: 1 argument */
    rb_define_method(cDBM, "[]", fdbm_aref, 1);

    /* ... */

    /* ID for a instance variable to store DBM data */
    id_dbm = rb_intern("dbm");
}

dbm 擴充套件使用 TypedData_Make_Struct 封裝 C 環境中的 dbm 結構。

struct dbmdata {
    int  di_size;
    DBM *di_dbm;
};

static const rb_data_type_t dbm_type = {
    "dbm",
    {0, free_dbm, memsize_dbm,},
    0, 0,
    RUBY_TYPED_FREE_IMMEDIATELY,
};

static VALUE
fdbm_alloc(VALUE klass)
{
    struct dbmdata *dbmp;
    /* Allocate T_DATA object and C struct and fill struct with zero bytes */
    return TypedData_Make_Struct(klass, struct dbmdata, &dbm_type, dbmp);
}

此程式碼將 dbmdata 結構封裝到 Ruby 物件中。我們避免直接封裝 DBM*,因為我們想要快取大小資訊。由於 Object.allocate 會配置一個一般的 T_OBJECT 類型 (而不是 T_DATA),因此使用 rb_define_alloc_func() 覆寫它或使用 rb_undef_alloc_func() 刪除它非常重要。

若要從 Ruby 物件擷取 dbmdata 結構,我們定義下列巨集

#define GetDBM(obj, dbmp) do {\
    TypedData_Get_Struct((obj), struct dbmdata, &dbm_type, (dbmp));\
    if ((dbmp) == 0) closed_dbm();\
    if ((dbmp)->di_dbm == 0) closed_dbm();\
} while (0)

這種複雜的巨集會執行 DBM 的擷取和關閉檢查。

有下列三種方式接收方法引數。首先,具有固定引數數目的方法會像這樣接收引數

static VALUE
fdbm_aref(VALUE obj, VALUE keystr)
{
    struct dbmdata *dbmp;
    GetDBM(obj, dbmp);
    /* Use dbmp to access the key */
    dbm_fetch(dbmp->di_dbm, StringValueCStr(keystr));
    /* ... */
}

C 函式的第一個引數是 self,其餘則是方法的引數。

其次,具有任意引數數目的方法會像這樣接收引數

static VALUE
fdbm_s_open(int argc, VALUE *argv, VALUE klass)
{
    /* ... */
    if (rb_scan_args(argc, argv, "11", &file, &vmode) == 1) {
        mode = 0666;          /* default value */
    }
    /* ... */
}

第一個引數是方法引數的數目,第二個引數是方法引數的 C 陣列,而第三個引數則是方法的接收器。

您可以使用函式 rb_scan_args() 來檢查和擷取引數。第三個引數是一個字串,用來指定如何擷取方法引數並將其指定給下列的 VALUE 參照。

您可以使用 rb_check_arity() 檢查引數數目,這在您想要將引數視為清單時很方便。

以下是使用 Ruby 陣列接收引數的方法範例

static VALUE
thread_initialize(VALUE thread, VALUE args)
{
    /* ... */
}

第一個引數是接收器,第二個引數是包含傳遞給該方法的引數的 Ruby 陣列。

注意GC 應了解參照 Ruby 物件的全球變數,但不會匯出到 Ruby 世界。您需要透過下列方式保護它們

void rb_global_variable(VALUE *var)

或透過下列方式保護物件本身

void rb_gc_register_mark_object(VALUE object)

準備 extconf.rb

如果存在名為 extconf.rb 的檔案,系統會執行它來產生 Makefile。

extconf.rb 是用來檢查編譯條件等的檔案。您需要將下列內容

require 'mkmf'

置於檔案的最上方。您可以使用下列函式來檢查各種條件。

append_cppflags(array-of-flags[, opt]): append each flag to $CPPFLAGS if usable
append_cflags(array-of-flags[, opt]): append each flag to $CFLAGS if usable
append_ldflags(array-of-flags[, opt]): append each flag to $LDFLAGS if usable
have_macro(macro[, headers[, opt]]): check whether macro is defined
have_library(lib[, func[, headers[, opt]]]): check whether library containing function exists
find_library(lib[, func, *paths]): find library from paths
have_func(func[, headers[, opt]): check whether function exists
have_var(var[, headers[, opt]]): check whether variable exists
have_header(header[, preheaders[, opt]]): check whether header file exists
find_header(header, *paths): find header from paths
have_framework(fw): check whether framework exists (for MacOS X)
have_struct_member(type, member[, headers[, opt]]): check whether struct has member
have_type(type[, headers[, opt]]): check whether type exists
find_type(type, opt, *headers): check whether type exists in headers
have_const(const[, headers[, opt]]): check whether constant is defined
check_sizeof(type[, headers[, opts]]): check size of type
check_signedness(type[, headers[, opts]]): check signedness of type
convertible_int(type[, headers[, opts]]): find convertible integer type
find_executable(bin[, path]): find executable file path
create_header(header): generate configured header
create_makefile(target[, target_prefix]): generate Makefile

請參閱 MakeMakefile 以取得這些函式的完整文件。

下列變數的值會影響 Makefile。

$CFLAGS: included in CFLAGS make variable (such as -O)
$CPPFLAGS: included in CPPFLAGS make variable (such as -I, -D)
$LDFLAGS: included in LDFLAGS make variable (such as -L)
$objs: list of object file names

編譯器/連結器旗標通常無法移植,您應分別使用 append_cppflagsappend_cpflagsappend_ldflags,而不是直接附加上述變數。

一般來說,物件檔案清單會透過搜尋原始檔自動產生,但如果在建置時會產生任何來源,您必須明確定義它們。

如果編譯條件未滿足,您不應呼叫「create_makefile」。Makefile 將不會產生,編譯不會執行。

準備 depend(選用)

如果名為 depend 的檔案存在,Makefile 將包含該檔案以檢查相依性。您可以透過呼叫來建立此檔案

% gcc -MM *.c > depend

這無害。準備它。

產生 Makefile

嘗試透過以下方式產生 Makefile

ruby extconf.rb

如果函式庫應安裝在 vendor_ruby 目錄而非 site_ruby 目錄中,請使用 –vendor 選項,如下所示。

ruby extconf.rb --vendor

如果您將擴充函式庫放置在 Ruby 原始碼樹的 ext 目錄中,您不需要此步驟。在這種情況下,直譯器的編譯將為您執行此步驟。

執行 make

輸入

make

編譯您的擴充函式。如果您已將擴充函式庫放置在 Ruby 原始碼樹的 ext 目錄中,您也不需要此步驟。

偵錯

您可能需要 rb_debug 擴充函式。可以透過在 ext/Setup 檔案中新增目錄名稱,以靜態方式連結擴充函式,以便您可以使用偵錯器檢查擴充函式。

完成!現在您有了擴充函式庫

您可以對您的函式庫執行任何您想做的事。Ruby 作者不會對依賴於 Ruby API 的您的程式碼主張任何限制。請隨時使用、修改、散布或販賣您的程式。

附錄 A. Ruby 標頭和原始碼檔案概觀

Ruby 標頭檔案

$repo_root/include/ruby 下的所有內容都已安裝 make install。它應從 C 擴充函式中包含在 #include <ruby.h> 中。所有符號都是公開 API,但以 rbimpl_RBIMPL_ 為字首的符號除外。它們是實作細節,不應由 C 擴充函式使用。

只有對應巨集定義在 $repo_root/include/ruby.h 標頭中的 $repo_root/include/ruby/*.h 才允許 C 擴充函式 #include-d。

$repo_root/internal/ 下或直接在根目錄 $repo_root/*.h 下的標頭檔案不會建立安裝。它們是僅包含內部 API 的內部標頭。

Ruby 語言核心

class.c

類別和模組

error.c

例外類別和例外機制

gc.c

記憶體管理

load.c

函式庫載入

object.c

物件

variable.c

變數和常數

Ruby 語法剖析器

parse.y

語法定義

parse.c

自動從 parse.y 產生

defs/keywords

保留關鍵字

lex.c

自動從關鍵字產生

Ruby 評估器 (又稱 YARV)

compile.c
eval.c
eval_error.c
eval_jump.c
eval_safe.c
insns.def           : definition of VM instructions
iseq.c              : implementation of VM::ISeq
thread.c            : thread management and context switching
thread_win32.c      : thread implementation
thread_pthread.c    : ditto
vm.c
vm_dump.c
vm_eval.c
vm_exec.c
vm_insnhelper.c
vm_method.c

defs/opt_insns_unif.def  : instruction unification
defs/opt_operand.def     : definitions for optimization

  -> insn*.inc           : automatically generated
  -> opt*.inc            : automatically generated
  -> vm.inc              : automatically generated

正規表示式引擎 (Onigumo)

regcomp.c
regenc.c
regerror.c
regexec.c
regparse.c
regsyntax.c

公用函式

debug.c

C 除錯器的除錯符號

dln.c

動態載入

st.c

一般用途雜湊表

strftime.c

格式化時間

util.c

其他公用程式

Ruby 詮釋器實作

dmyext.c
dmydln.c
dmyencoding.c
id.c
inits.c
main.c
ruby.c
version.c

gem_prelude.rb
prelude.rb

Class 函式庫

array.c

陣列

bignum.c

大數字

compar.c

可比較

complex.c

複數

cont.c

FiberContinuation

dir.c

目錄

enum.c

可列舉

enumerator.c

列舉器

file.c

檔案

hash.c

雜湊

io.c

IO

marshal.c

封送

math.c

數學

numeric.c

NumericInteger、Fixnum、Float

pack.c

Array#packString#unpack

proc.c

BindingProc

process.c

處理程序

random.c

亂數

range.c

範圍

rational.c

有理數

re.c

RegexpMatchData

signal.c

訊號

sprintf.c

String#sprintf

string.c

字串

struct.c

結構

time.c

時間

defs/known_errors.def

Errno::* 例外類別

-> known_errors.inc

自動產生

多語化

encoding.c

編碼

transcode.c

編碼::轉換器

enc/*.c

編碼類別

enc/trans/*

碼點對應表

goruby 解譯器實作

goruby.c
golf_prelude.rb     : goruby specific libraries.
  -> golf_prelude.c : automatically generated

附錄 B. Ruby 擴充 API 參考

型別

VALUE

Ruby 物件的型別。實際結構定義在 ruby.h 中,例如 struct RString 等。要參考結構中的值,請使用轉型巨集,例如 RSTRING(obj)。

變數與常數

Qnil

nil 物件

Qtrue

true 物件(預設 true 值)

Qfalse

false 物件

C 指標包裝

Data_Wrap_Struct(VALUE klass, void (*mark)(), void (*free)(), void *sval)

將 C 指標包裝成 Ruby 物件。如果物件有參考到其他 Ruby 物件,則應在 GC 處理過程中使用 mark 函式標記它們。否則,mark 應為 0。當不再有任何地方參考此物件時,指標將會由 free 函式捨棄。

Data_Make_Struct(klass, type, mark, free, sval)

此巨集使用 malloc() 配置記憶體,將其指定給變數 sval,並傳回封裝指向記憶體區域的指標的 DATA。

Data_Get_Struct(data, type, sval)

此巨集從 DATA 擷取指標值,並將其指定給變數 sval。

檢查 VALUE 型別

RB_TYPE_P(value, type)

value 是內部型別(T_NIL、T_FIXNUM 等)嗎?

TYPE(value)

內部型別(T_NIL、T_FIXNUM 等)

FIXNUM_P(value)

value 是 Fixnum 嗎?

NIL_P(value)

value 是 nil 嗎?

RB_INTEGER_TYPE_P(value)

valueInteger 嗎?

RB_FLOAT_TYPE_P(value)

valueFloat 嗎?

void Check_Type(VALUE value, int type)

確保 value 是給定的內部 type 或引發 TypeError

VALUE 類型轉換

FIX2INT(value), INT2FIX(i)

Fixnum <-> 整數

FIX2LONG(value), LONG2FIX(l)

Fixnum <-> 長整數

NUM2INT(value), INT2NUM(i)

Numeric <-> 整數

NUM2UINT(value), UINT2NUM(ui)

Numeric <-> 無符號整數

NUM2LONG(value), LONG2NUM(l)

Numeric <-> 長整數

NUM2ULONG(value), ULONG2NUM(ul)

Numeric <-> 無符號長整數

NUM2LL(value), LL2NUM(ll)

Numeric <-> 長長整數

NUM2ULL(value), ULL2NUM(ull)

Numeric <-> 無符號長長整數

NUM2OFFT(value), OFFT2NUM(off)

Numeric <-> off_t

NUM2SIZET(value), SIZET2NUM(size)

Numeric <-> size_t

NUM2SSIZET(value), SSIZET2NUM(ssize)

Numeric <-> ssize_t

rb_integer_pack(value, words, numwords, wordsize, nails, flags), rb_integer_unpack(words, numwords, wordsize, nails, flags)

Numeric <-> 任意大小的整數緩衝區

NUM2DBL(value)

Numeric -> double

rb_float_new(f)

double -> Float

RSTRING_LEN(str)

String -> String 資料長度(以位元組為單位)

RSTRING_PTR(str)

String -> String 資料指標,請注意,結果指標可能沒有以 NUL 終止

StringValue(value)

Object,其中 #to_str -> String

StringValuePtr(value)

Object,其中 #to_str -> String 資料指標

StringValueCStr(value)

Object,其中 #to_str -> String 資料指標,不含 NUL 位元組,保證結果資料以 NUL 終止

rb_str_new2(s)

char * -> String

定義類別和模組

VALUE rb_define_class(const char *name, VALUE super)

將新的 Ruby 類別定義為 super 的子類別。

VALUE rb_define_class_under(VALUE module, const char *name, VALUE super)

在模組的命名空間下,建立新的 Ruby 類別作為 super 的子類別。

VALUE rb_define_module(const char *name)

定義一個新的 Ruby 模組。

VALUE rb_define_module_under(VALUE module, const char *name)

在模組的命名空間下定義一個新的 Ruby 模組。

void rb_include_module(VALUE klass, VALUE module)

將模組包含到類別中。如果類別已經包含它,則忽略。

void rb_extend_object(VALUE object, VALUE module)

使用模組的屬性來擴充物件。

定義全域變數

void rb_define_variable(const char *name, VALUE *var)

定義一個在 C 和 Ruby 之間共用的全域變數。如果 name 包含一個不允許作為符號一部分的字元,則無法從 Ruby 程式中看到它。

void rb_define_readonly_variable(const char *name, VALUE *var)

定義一個唯讀的全域變數。運作方式就像 rb_define_variable(),除了定義的變數是唯讀的。

void rb_define_virtual_variable(const char *name, VALUE (*getter)(), void (*setter)())

定義一個虛擬變數,其行為由一對 C 函式定義。當變數被參照時,會呼叫 getter 函式。當變數被設定為一個值時,會呼叫 setter 函式。getter/setter 函式的原型如下

VALUE getter(ID id)
void setter(VALUE val, ID id)

getter 函式必須傳回存取的值。

void rb_define_hooked_variable(const char *name, VALUE *var, VALUE (*getter)(), void (*setter)())

定義掛鉤變數。它是一個具有 C 變數的虛擬變數。getter 會被呼叫為

VALUE getter(ID id, VALUE *var)

傳回一個新值。setter 會被呼叫為

void setter(VALUE val, ID id, VALUE *var)
void rb_global_variable(VALUE *var)

告訴 GC 保護 C 全域變數,該變數持有要標記的 Ruby 值。

void rb_gc_register_mark_object(VALUE object)

告訴 GC 保護 object,它可能在任何地方都沒有被參照。

常數定義

void rb_define_const(VALUE klass, const char *name, VALUE val)

在類別/模組下定義一個新的常數。

void rb_define_global_const(const char *name, VALUE val)

定義一個全域常數。這與

rb_define_const(rb_cObject, name, val)

Method 定義

rb_define_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)

為類別定義一個方法。func 是函式指標。argc 是參數的數量。如果 argc 為 -1,函式將接收 3 個參數:argc、argv 和 self。如果 argc 為 -2,函式將接收 2 個參數,self 和 args,其中 args 是方法參數的 Ruby 陣列。

rb_define_private_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)

為類別定義一個私有方法。參數與 rb_define_method() 相同。

rb_define_singleton_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)

定義單例方法。參數與 rb_define_method() 相同。

rb_check_arity(int argc, int min, int max)

檢查參數數量,argc 必須在 min..max 範圍內。如果 max 為 UNLIMITED_ARGUMENTS,則不檢查上限。如果 argc 超出範圍,則會引發 ArgumentError

rb_scan_args(int argc, VALUE *argv, const char *fmt, …)

根據格式字串,從 argc 和 argv 中擷取參數到指定的 VALUE 參照。格式可以用 ABNF 描述如下

scan-arg-spec  := param-arg-spec [keyword-arg-spec] [block-arg-spec]

param-arg-spec := pre-arg-spec [post-arg-spec] / post-arg-spec /
                  pre-opt-post-arg-spec
pre-arg-spec   := num-of-leading-mandatory-args [num-of-optional-args]
post-arg-spec  := sym-for-variable-length-args
                  [num-of-trailing-mandatory-args]
pre-opt-post-arg-spec := num-of-leading-mandatory-args num-of-optional-args
                         num-of-trailing-mandatory-args
keyword-arg-spec := sym-for-keyword-arg
block-arg-spec := sym-for-block-arg

num-of-leading-mandatory-args  := DIGIT ; The number of leading
                                        ; mandatory arguments
num-of-optional-args           := DIGIT ; The number of optional
                                        ; arguments
sym-for-variable-length-args   := "*"   ; Indicates that variable
                                        ; length arguments are
                                        ; captured as a ruby array
num-of-trailing-mandatory-args := DIGIT ; The number of trailing
                                        ; mandatory arguments
sym-for-keyword-arg            := ":"   ; Indicates that keyword
                                        ; argument captured as a hash.
                                        ; If keyword arguments are not
                                        ; provided, returns nil.
sym-for-block-arg              := "&"   ; Indicates that an iterator
                                        ; block should be captured if
                                        ; given

例如,「12」表示方法至少需要一個參數,最多接收三個 (1+2) 個參數。因此,格式字串後面必須接著三個變數參照,用於指定擷取的參數。對於省略的參數,變數會設定為 Qnil。變數參照可以替換為 NULL,表示對應的擷取參數應該略過。

傳回指定的參數數量,不包含選項雜湊或迭代器區塊。

rb_scan_args_kw(int kw_splat, int argc, VALUE *argv, const char *fmt, …)

rb_scan_args 相同,但 kw_splat 參數指定是否提供關鍵字參數 (而不是由 Ruby 對 C 函式的呼叫決定)。kw_splat 應該是下列值之一

RB_SCAN_ARGS_PASS_CALLED_KEYWORDS

rb_scan_args 行為相同。

RB_SCAN_ARGS_KEYWORDS

最後一個參數應該是當作關鍵字的雜湊。

RB_SCAN_ARGS_LAST_HASH_KEYWORDS

如果最後一個參數是雜湊,則當作關鍵字處理,否則不當作關鍵字處理。

int rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values)

擷取與關鍵字關聯的參數 VALUE,由 table 指向 values,並在過程中從 keyword_hash 中刪除擷取的項目。table 參照的第一個 required 個 ID 是強制性的,後續的 optional 個 ID (- optional - 1 如果 optional 為負值) 是可選的。如果 keyword_hash 中不包含強制性關鍵字,則會引發「缺少關鍵字」ArgumentError。如果 keyword_hash 中不存在可選關鍵字,則 values 中對應的元素會設定為 Qundef。如果 optional 為負值,則會忽略 keyword_hash 的其餘部分,否則會引發「未知關鍵字」ArgumentError

警告,在 C API 中處理關鍵字參數的效率低於在 Ruby 中處理它們。考慮在非關鍵字 C 函數周圍使用 Ruby 封裝方法。參考:bugs.ruby-lang.org/issues/11339

VALUE rb_extract_keywords(VALUE *original_hash)

original_hash 參照的雜湊物件中,萃取其金鑰為符號的配對至新的雜湊中。如果原始雜湊包含非符號金鑰,則將它們複製到另一個雜湊中,並透過 original_hash 儲存新的雜湊,否則儲存 0。

呼叫 Ruby 方法

VALUE rb_funcall(VALUE recv, ID mid, int narg, …)

呼叫方法。若要從方法名稱中擷取 mid,請使用 rb_intern()。能夠呼叫私有/受保護的方法。

VALUE rb_funcall2(VALUE recv, ID mid, int argc, VALUE *argv)
VALUE rb_funcallv(VALUE recv, ID mid, int argc, VALUE *argv)

呼叫方法,並將參數傳遞為值陣列。能夠呼叫私有/受保護的方法。

VALUE rb_funcallv_kw(VALUE recv, ID mid, int argc, VALUE *argv, int kw_splat)

與 rb_funcallv 相同,使用 kw_splat 來判斷是否傳遞關鍵字參數。

VALUE rb_funcallv_public(VALUE recv, ID mid, int argc, VALUE *argv)

呼叫方法,並將參數傳遞為值陣列。只能呼叫公開的方法。

VALUE rb_funcallv_public_kw(VALUE recv, ID mid, int argc, VALUE *argv, int kw_splat)

與 rb_funcallv_public 相同,使用 kw_splat 來判斷是否傳遞關鍵字參數。

VALUE rb_funcall_passing_block(VALUE recv, ID mid, int argc, const VALUE* argv)

與 rb_funcallv_public 相同,除了在呼叫方法時,將目前作用中的區塊傳遞為區塊。

VALUE rb_funcall_passing_block_kw(VALUE recv, ID mid, int argc, const VALUE* argv, int kw_splat)

與 rb_funcall_passing_block 相同,使用 kw_splat 來判斷是否傳遞關鍵字參數。

VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE passed_procval)

與 rb_funcallv_public 相同,除了 passed_procval 指定要傳遞給方法的區塊。

VALUE rb_funcall_with_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE passed_procval, int kw_splat)

與 rb_funcall_with_block 相同,使用 kw_splat 來判斷是否傳遞關鍵字參數。

VALUE rb_eval_string(const char *str)

編譯並執行字串作為 Ruby 程式。

ID rb_intern(const char *name)

傳回對應於名稱的 ID。

char *rb_id2name(ID id)

傳回對應於 ID 的名稱。

char *rb_class2name(VALUE klass)

傳回類別的名稱。

int rb_respond_to(VALUE obj, ID id)

如果物件對應於 id 指定的訊息,則傳回 true。

執行個體變數

VALUE rb_iv_get(VALUE obj, const char *name)

擷取執行個體變數的值。如果名稱沒有以「@」為字首,則變數將無法從 Ruby 存取。

VALUE rb_iv_set(VALUE obj, const char *name, VALUE val)

設定執行個體變數的值。

控制結構

VALUE rb_block_call(VALUE recv, ID mid, int argc, VALUE * argv, VALUE (*func) (ANYARGS), VALUE data2)

使用符號 mid 指定的方法名稱,在 recv 上呼叫方法,並在 argv 中提供 argc 個引數,將 func 提供為區塊。當 func 作為區塊呼叫時,它會將 yield 的值接收為第一個引數,並將 data2 接收為第二個引數。當以多個值傳遞(在 C 中,rb_yield_values()、rb_yield_values2() 和 rb_yield_splat()),data2 會打包為 陣列,而傳遞的值則可透過第三/四個引數的 argc/argv 取得。

VALUE rb_block_call_kw(VALUE recv, ID mid, int argc, VALUE * argv, VALUE (*func) (ANYARGS), VALUE data2, int kw_splat)

與 rb_funcall_with_block 相同,使用 kw_splat 來判斷是否傳遞關鍵字參數。

[已過時] VALUE rb_iterate(VALUE (*func1)(), VALUE arg1, VALUE (*func2)(), VALUE arg2)

呼叫函式 func1,並將 func2 提供為區塊。func1 會以引數 arg1 呼叫。func2 會將 yield 的值接收為第一個引數,並將 arg2 接收為第二個引數。

當 rb_iterate 在 1.9 中使用時,func1 必須呼叫某些 Ruby 層級的方法。此函式自 1.9 起已過時;請改用 rb_block_call。

VALUE rb_yield(VALUE val)

將 val 作為單一引數傳遞給區塊。

VALUE rb_yield_values(int n, …)

n 個引數傳遞給區塊,每個 Ruby 引數使用一個 C 引數。

VALUE rb_yield_values2(int n, VALUE *argv)

n 個引數傳遞給區塊,所有 Ruby 引數都位於 C argv 陣列中。

VALUE rb_yield_values_kw(int n, VALUE *argv, int kw_splat)

與 rb_yield_values2 相同,使用 kw_splat 來判斷是否傳遞關鍵字引數。

VALUE rb_yield_splat(VALUE args)

與 rb_yield_values2 相同,但引數是由 Ruby 陣列 args 指定。

VALUE rb_yield_splat_kw(VALUE args, int kw_splat)

與 rb_yield_splat 相同,使用 kw_splat 來判斷是否傳遞關鍵字參數。

VALUE rb_rescue(VALUE (*func1)(ANYARGS), VALUE arg1, VALUE (*func2)(ANYARGS), VALUE arg2)

呼叫函式 func1,並將 arg1 作為參數。如果在 func1 期間發生例外狀況,則會呼叫 func2,並將 arg2 作為第一個參數,將例外狀況物件作為第二個參數。如果沒有發生例外狀況,則 rb_rescue() 的傳回值會是 func1 的傳回值;否則,傳回值會是 func2 的傳回值。

VALUE rb_ensure(VALUE (*func1)(ANYARGS), VALUE arg1, VALUE (*func2)(ANYARGS), VALUE arg2)

呼叫函式 func1,並將 arg1 作為參數,如果執行已終止,則呼叫 func2,並將 arg2 作為參數。如果沒有發生例外狀況,則 rb_ensure() 的傳回值會是 func1 的傳回值。

VALUE rb_protect(VALUE (*func) (VALUE), VALUE arg, int *state)

呼叫函式 func,並將 arg 作為參數。如果在 func 期間沒有發生例外狀況,則會傳回 func 的結果,而 *state 會是零。否則,會傳回 Qnil,並將 *state 設為非零。如果 state 為 NULL,則在兩種情況下都不會設定。忽略已捕獲的例外狀況時,必須使用 rb_set_errinfo(Qnil) 來清除錯誤資訊。

void rb_jump_tag(int state)

繼續由 rb_protect() 和 rb_eval_string_protect() 捕獲的例外狀況。state 必須是這些函式的傳回值。此函式絕不會傳回給呼叫者。

void rb_iter_break()

退出目前最內層的區塊。此函式絕不會傳回給呼叫者。

void rb_iter_break_value(VALUE value)

帶著值退出目前最內層的區塊。區塊會傳回指定的參數值。此函式絕不會傳回給呼叫者。

例外狀況和錯誤

void rb_warn(const char *fmt, …)

根據類似 printf 的格式列印警告訊息。

void rb_warning(const char *fmt, …)

如果 $VERBOSE 為 true,則根據類似 printf 的格式列印警告訊息。

void rb_raise(rb_eRuntimeError, const char *fmt, …)

引發 RuntimeError。fmt 是類似 printf() 的格式字串。

void rb_raise(VALUE exception, const char *fmt, …)

引發類別例外狀況。fmt 是類似 printf() 的格式字串。

void rb_fatal(const char *fmt, …)

引發致命錯誤,終止直譯器。致命錯誤不會執行例外狀況處理,但會執行 ensure 區塊。

void rb_bug(const char *fmt, …)

立即終止直譯器。此函式應在直譯器發生錯誤的情況下呼叫。不會執行例外狀況處理或 ensure 執行。

注意:在格式字串中,「%」PRIsVALUE 可用於 Object#to_s(或如果設定「+」旗標,則為 Object#inspect)輸出(且相關引數必須為 VALUE)。由於它與「%i」衝突,對於格式字串中的整數,請使用「%d」。

執行緒

從 Ruby 1.9 開始,Ruby 支援原生 1:1 執行緒,每個 Ruby Thread 物件有一個核心執行緒。目前有一個 GVL (全域 VM 鎖定),可防止同時執行 Ruby 程式碼,而 rb_thread_call_without_gvl 和 rb_thread_call_without_gvl2 函式可以解除此鎖定。這些函式使用起來很棘手,並記載於 thread.c 中;在閱讀 thread.c 中的註解之前,請勿使用這些函式。

void rb_thread_schedule(void)

給排程器一個提示,將執行傳遞給另一個執行緒。

單一檔案描述符的輸入/輸出 (IO)

int rb_io_wait_readable(int fd)

無限期地等待指定的檔案描述符變為可讀取,允許排程其他執行緒。如果可以執行讀取,則傳回 true 值;如果發生無法復原的錯誤,則傳回 false 值。

int rb_io_wait_writable(int fd)

如同 rb_io_wait_readable,但針對可寫入性。

int rb_wait_for_single_fd(int fd, int events, struct timeval *timeout)

允許在單一檔案描述符上等待一個或多個事件,並指定逾時時間。

events 是下列值的任意組合的遮罩

  • RB_WAITFD_IN - 等待正常資料的可讀取性

  • RB_WAITFD_OUT - 等待可寫入性

  • RB_WAITFD_PRI - 等待緊急資料的可讀取性

使用 NULL timeout 無限期地等待。

I/O 多工

Ruby 支援基於 select(2) 系統呼叫的 I/O 多工。Linux select_tut(2) 手冊頁 <man7.org/linux/man-pages/man2/select_tut.2.html> 提供了如何使用 select(2) 的良好概觀,而 Ruby API 具有與眾所周知的 select API 類似的函數和資料結構。瞭解 select(2) 是瞭解本節的必要條件。

typedef struct rb_fdset_t

包裝 select(2) 使用的 fd_set 位元圖的資料結構。這允許 Ruby 使用比現代平台上的歷史限制允許的更大的檔案描述符集。

void rb_fd_init(rb_fdset_t *)

初始化 rb_fdset_t,在其他 rb_fd_* 操作之前必須先初始化。類似於呼叫 malloc(3) 來配置 fd_set。

void rb_fd_term(rb_fdset_t *)

銷毀 rb_fdset_t,釋放它使用的任何記憶體和資源。在未來使用之前,必須使用 rb_fd_init 重新初始化。類似於呼叫 free(3) 來釋放 fd_set 的記憶體。

void rb_fd_zero(rb_fdset_t *)

從 rb_fdset_t 清除所有檔案描述符,類似於 FD_ZERO(3)。

void rb_fd_set(int fd, rb_fdset_t *)

在 rb_fdset_t 中新增指定的檔案描述符,類似於 FD_SET(3)。

void rb_fd_clr(int fd, rb_fdset_t *)

從 rb_fdset_t 中移除指定的檔案描述符,類似於 FD_CLR(3)。

int rb_fd_isset(int fd, const rb_fdset_t *)

如果在 rb_fdset_t 中設定了指定的檔案描述符,則傳回 true;否則傳回 false。類似於 FD_ISSET(3)。

int rb_thread_fd_select(int nfds, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout)

類似於 select(2) 系統呼叫,但允許在等待時排程其他 Ruby 執行緒。

當僅等待單一 FD 時,偏好使用 rb_io_wait_readable、rb_io_wait_writable 或 rb_wait_for_single_fd 函式,因為它們可以針對特定平台進行最佳化(目前僅限 Linux)。

初始化並啟動直譯器

嵌入式 API 函式如下(擴充套件函式庫不需要)

void ruby_init()

初始化直譯器。

void *ruby_options(int argc, char **argv)

Process 直譯器的命令列引數。並編譯要執行的 Ruby 程式碼。它會傳回編譯程式碼的不透明指標或內部特殊值。

int ruby_run_node(void *n)

執行給定的編譯程式碼並結束此程序。如果成功執行程式碼,它會傳回 EXIT_SUCCESS。否則,它會傳回其他值。

void ruby_script(char *name)

指定腳本的名稱($0)。

直譯器事件的掛鉤

void rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)

為指定的直譯器事件新增掛鉤函式。events 應為下列值的 OR 值

RUBY_EVENT_LINE
RUBY_EVENT_CLASS
RUBY_EVENT_END
RUBY_EVENT_CALL
RUBY_EVENT_RETURN
RUBY_EVENT_C_CALL
RUBY_EVENT_C_RETURN
RUBY_EVENT_RAISE
RUBY_EVENT_ALL

rb_event_hook_func_t 的定義如下

typedef void (*rb_event_hook_func_t)(rb_event_t event, VALUE data,
                                     VALUE self, ID id, VALUE klass)

傳遞給 rb_add_event_hook() 的第三個引數「data」會作為第二個引數傳遞給掛鉤函式,在 1.8 中,它是指向目前 NODE 的指標。請參閱下方的 RB_EVENT_HOOKS_HAVE_CALLBACK_DATA。

int rb_remove_event_hook(rb_event_hook_func_t func)

移除指定的掛鉤函式。

記憶體使用

void rb_gc_adjust_memory_usage(ssize_t diff)

調整已註冊外部記憶體的數量。您可以透過此函式告訴 GC 外部函式庫使用了多少記憶體。使用正數 diff 呼叫此函式表示記憶體使用量增加;配置新的記憶體區塊或將區塊重新配置為較大的大小。使用負數 diff 呼叫此函式表示記憶體使用量減少;釋放記憶體區塊或將區塊重新配置為較小的尺寸。此函式可能會觸發 GC

相容性巨集

預設提供一些巨集來檢查 API 相容性。

NORETURN_STYLE_NEW

表示 NORETURN 巨集是函數樣式,而非前置詞。

HAVE_RB_DEFINE_ALLOC_FUNC

表示提供函數 rb_define_alloc_func(),表示使用配置架構。這與 have_func(“rb_define_alloc_func”, “ruby.h”) 的結果相同。

HAVE_RB_REG_NEW_STR

表示提供函數 rb_reg_new_str(),用於從 String 物件建立 Regexp 物件。這與 have_func(“rb_reg_new_str”, “ruby.h”) 的結果相同。

HAVE_RB_IO_T

表示提供類型 rb_io_t。

USE_SYMBOL_AS_METHOD_NAME

表示符號將回傳為方法名稱,例如 Module#methods、#singleton_methods 等。

HAVE_RUBY_*_H

定義於 ruby.h 中,表示提供對應的標頭。例如,當定義 HAVE_RUBY_ST_H 時,您應該使用 ruby/st.h,而不是單純的 st.h。

對應於這些巨集的標頭檔案可以從擴充程式函式庫直接 #include

RB_EVENT_HOOKS_HAVE_CALLBACK_DATA

表示 rb_add_event_hook() 會採用第三個引數 'data',傳遞給指定的事件掛鉤函數。

定義關鍵字引數函數的向下相容性巨集

大多數 Ruby C 擴充程式都設計為支援多個 Ruby 版本。為了正確支援 Ruby 2.7+ 中的關鍵字引數分離,C 擴充程式需要使用 *_kw 函數。然而,這些函數在 Ruby 2.6 及以下版本中不存在,因此在這些情況下,應該定義巨集,讓您可以在多個 Ruby 版本上使用相同的程式碼。以下是您可以在擴充程式中使用的範例巨集,這些擴充程式支援 Ruby 2.6(或以下版本),同時使用 Ruby 2.7 中引入的 *_kw 函數。

#ifndef RB_PASS_KEYWORDS
/* Only define macros on Ruby <2.7 */
#define rb_funcallv_kw(o, m, c, v, kw) rb_funcallv(o, m, c, v)
#define rb_funcallv_public_kw(o, m, c, v, kw) rb_funcallv_public(o, m, c, v)
#define rb_funcall_passing_block_kw(o, m, c, v, kw) rb_funcall_passing_block(o, m, c, v)
#define rb_funcall_with_block_kw(o, m, c, v, b, kw) rb_funcall_with_block(o, m, c, v, b)
#define rb_scan_args_kw(kw, c, v, s, ...) rb_scan_args(c, v, s, __VA_ARGS__)
#define rb_call_super_kw(c, v, kw) rb_call_super(c, v)
#define rb_yield_values_kw(c, v, kw) rb_yield_values2(c, v)
#define rb_yield_splat_kw(a, kw) rb_yield_splat(a)
#define rb_block_call_kw(o, m, c, v, f, p, kw) rb_block_call(o, m, c, v, f, p)
#define rb_fiber_resume_kw(o, c, v, kw) rb_fiber_resume(o, c, v)
#define rb_fiber_yield_kw(c, v, kw) rb_fiber_yield(c, v)
#define rb_enumeratorize_with_size_kw(o, m, c, v, f, kw) rb_enumeratorize_with_size(o, m, c, v, f)
#define SIZED_ENUMERATOR_KW(obj, argc, argv, size_fn, kw_splat) \
    rb_enumeratorize_with_size((obj), ID2SYM(rb_frame_this_func()), \
                               (argc), (argv), (size_fn))
#define RETURN_SIZED_ENUMERATOR_KW(obj, argc, argv, size_fn, kw_splat) do { \
        if (!rb_block_given_p())                                            \
            return SIZED_ENUMERATOR(obj, argc, argv, size_fn);              \
    } while (0)
#define RETURN_ENUMERATOR_KW(obj, argc, argv, kw_splat) RETURN_SIZED_ENUMERATOR(obj, argc, argv, 0)
#define rb_check_funcall_kw(o, m, c, v, kw) rb_check_funcall(o, m, c, v)
#define rb_obj_call_init_kw(o, c, v, kw) rb_obj_call_init(o, c, v)
#define rb_class_new_instance_kw(c, v, k, kw) rb_class_new_instance(c, v, k)
#define rb_proc_call_kw(p, a, kw) rb_proc_call(p, a)
#define rb_proc_call_with_block_kw(p, c, v, b, kw) rb_proc_call_with_block(p, c, v, b)
#define rb_method_call_kw(c, v, m, kw) rb_method_call(c, v, m)
#define rb_method_call_with_block_kw(c, v, m, b, kw) rb_method_call_with_block(c, v, m, b)
#define rb_eval_cmd_kwd(c, a, kw) rb_eval_cmd(c, a, 0)
#endif

附錄 C. 可用於 extconf.rb 的函數

請參閱 mkmf 的文件。

附錄 D. 世代 GC

Ruby 2.1 導入了世代垃圾收集器(稱為 RGenGC)。RGenGC(大多數)保持相容性。

一般來說,世代式GCen.wikipedia.org/wiki/Garbage_collection_%28computer_science%29)的擴充函式庫需要使用寫入屏障技術。RGenGC 在擴充函式庫中不使用寫入屏障也能正常運作。

如果您的函式庫遵守下列建議,效能可以進一步提升。特別是「不要直接觸碰指標」這一段很重要。

不相容性

您不能直接寫入 RBASIC(obj)->klass 欄位,因為它現在是常數值。

基本上您不應該寫入這個欄位,因為 MRI 預期它是一個不可變的欄位,但如果您想要在擴充函式庫中這麼做,可以使用下列函式

VALUE rb_obj_hide(VALUE obj)

清除 RBasic::klass 欄位。物件將會變成內部物件。ObjectSpace::each_object 無法找到這個物件。

VALUE rb_obj_reveal(VALUE obj, VALUE klass)

將 RBasic::klass 重設為 klass。我們預期「klass」是 rb_obj_hide() 隱藏的類別。

寫入屏障

RGenGC 不需要寫入屏障來支援世代式GC。然而,注意寫入屏障可以提升 RGenGC 的效能。請查看下列建議。

不要直接觸碰指標

在 MRI(include/ruby/ruby.h)中,支援一些巨集來取得內部資料結構的指標,例如 RARRAY_PTR()、RSTRUCT_PTR() 等。

不要使用這些巨集,請改用對應的 C API,例如 rb_ary_aref()、rb_ary_store() 等。

考慮是否要插入寫入屏障

如果您只使用內建型別,您不需要注意寫入屏障。

如果您支援 T_DATA 物件,您可以考慮使用寫入屏障。

只對下列型別物件插入寫入屏障才有用:(a)長駐物件,(b)產生大量物件時,(c)具有其他物件參考的容器型物件。如果您的擴充函式庫提供此類型的 T_DATA 物件,請考慮插入寫入屏障。

(a):短暫物件不會變成舊世代物件。(b):只有少數舊世代物件不會對效能造成影響。(c):只有少數參考不會對效能造成影響。

插入寫入障礙是一種非常困難的技巧,很容易引入重大錯誤。而且插入寫入障礙有幾個方面的開銷。基本上我們不建議您插入寫入障礙。請仔細考慮風險。

與內建類型結合

請考慮使用內建類型。大多數內建類型都支援寫入障礙,因此您可以使用它們來避免手動插入寫入障礙。

例如,如果您的 T_DATA 有對其他物件的參照,則可以將這些參照移到 Array。T_DATA 物件只有一個對陣列物件的參照。或者您也可以使用 Struct 物件來收集一個 T_DATA 物件(沒有任何參照)和一個包含參照的 Array

使用此類技術,您就不再需要插入寫入障礙了。

插入寫入障礙

[再次] 插入寫入障礙是一種非常困難的技巧,而且很容易引入重大錯誤。而且插入寫入障礙有幾個方面的開銷。基本上我們不建議您插入寫入障礙。請仔細考慮風險。

在插入寫入障礙之前,您需要了解 RGenGC 演算法(gc.c 將會幫助您)。可用的巨集和函式用於在 include/ruby/ruby.h 中插入寫入障礙。iseq.c 中提供了一個範例。

有關 RGenGC 和寫入障礙的完整指南,請參閱 <bugs.ruby-lang.org/projects/ruby-master/wiki/RGenGC>。

附錄 E. RB_GC_GUARD 可防止過早的 GC

C Ruby 目前使用保守式垃圾回收,因此 VALUE 變數必須保持在堆疊或暫存器上可見,以確保任何關聯資料保持可用。最佳化 C 編譯器並未考慮保守式垃圾回收,因此它們可能會最佳化原始 VALUE,即使程式碼依賴於與該 VALUE 關聯的資料。

以下範例說明如何使用 RB_GC_GUARD 來確保 sptr 的內容在執行 rb_str_new_cstr 的第二次呼叫時保持有效。

VALUE s, w;
const char *sptr;

s = rb_str_new_cstr("hello world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
sptr = RSTRING_PTR(s);
w = rb_str_new_cstr(sptr + 6); /* Possible GC invocation */

RB_GC_GUARD(s); /* ensure s (and thus sptr) do not get GC-ed */

在上述範例中,RB_GC_GUARD 必須置於 sptr 的最後一次使用之後。在取消參考 sptr 之前置入 RB_GC_GUARD 將毫無用處。RB_GC_GUARD 僅對 VALUE 資料類型有效,不對轉換的 C 資料類型有效。

如果在取消引用 sptr 後對「s」VALUE 進行非內聯函式呼叫,則在上述範例中根本不需要 RB_GC_GUARD。因此,在上述範例中,對「s」呼叫任何未內聯的函式,例如

rb_str_modify(s);

將確保「s」保留在堆疊或暫存器中,以防止 GC 呼叫過早釋放它。

使用 RB_GC_GUARD 巨集比在 C 中使用「volatile」關鍵字更可取。RB_GC_GUARD 具有下列優點

  1. 巨集使用的意圖很清楚

  2. RB_GC_GUARD 僅影響其呼叫位置,「volatile」會在每次使用變數時產生一些額外程式碼,損害最佳化。

  3. 「volatile」實作在某些編譯器和架構中可能是錯誤/不一致的。RB_GC_GUARD 可自訂為損壞的系統/編譯器,而不會對其他系統產生負面影響。

附錄 F. Ractor 支援

Ractor 是 Ruby 3.0 中引入的平行執行機制。所有 Ractor 都可以在不同的作業系統執行緒上平行執行(使用底層系統提供的執行緒),因此 C 擴充功能應具備執行緒安全性。可以在多個 Ractor 中執行的 C 擴充功能稱為「Ractor 安全」。

Ractor 安全性在 C 擴充功能周圍具有下列屬性

  1. 預設情況下,所有 C 擴充功能都被視為 Ractor 不安全。

  2. Ractor 不安全的 C 方法只能從主 Ractor 呼叫。如果由非主 Ractor 呼叫,則會引發 Ractor::UnsafeError

  3. 如果擴充功能想要標記為 Ractor 安全,則擴充功能應在擴充功能的 Init_ 函式中呼叫 rb_ext_ractor_safe(true),且所有定義的方法都將標記為 Ractor 安全。

若要建立「Ractor 安全」的 C 擴充功能,我們需要檢查下列各點

(1) 不要在 Ractor 之間共用不可共用的物件

例如,C 的全域變數可能會導致在 Ractor 之間共用不可共用的物件。

VALUE g_var;
VALUE set(VALUE self, VALUE v){ return g_var = v; }
VALUE get(VALUE self){ return g_var; }

set() 和 get() 配對可以使用 g_var 共用不可共用的物件,且這是不安全的 Ractor。

不僅直接使用全域變數,某些間接資料結構(例如全域 st_table)也可以共用物件,因此請小心。

請注意,類別和模組物件是可共用的物件,因此您可以將程式碼「cFoo = rb_define_class(…)」保留在 C 的全域變數中。

(2) 檢查擴充功能的執行緒安全性

擴充功能應具備執行緒安全性。例如,下列程式碼不具備執行緒安全性

bool g_called = false;
VALUE call(VALUE self) {
  if (g_called) rb_raise("recursive call is not allowed.");
  g_called = true;
  VALUE ret = do_something();
  g_called = false;
  return ret;
}

因為 g_called 全域變數應由其他 Ractor 的執行緒同步。若要避免這種資料競爭,應使用一些同步。請檢查 include/ruby/thread_native.h 和 include/ruby/atomic.h。

使用 Ractors 時,所有作為方法參數和接收器 (self) 傳入的物件都保證來自目前的 Ractor 或可共用。因此,與讓程式碼具備一般執行緒安全性相比,讓程式碼具備 ractor 安全性較為容易。例如,我們不需要鎖定陣列物件來存取其元素。

(3) 檢查任何已使用函式庫的執行緒安全性

如果擴充套件仰賴外部函式庫,例如函式庫 libfoo 中的函式 foo(),則函式 libfoo foo() 應具備執行緒安全性。

(4) 讓物件可共用

這並非讓擴充套件具備 Ractor 安全性的必要條件。

如果擴充套件提供由 rb_data_type_t 定義的特殊物件,請考慮這些物件是否可以共用。

RUBY_TYPED_FROZEN_SHAREABLE 旗標表示如果物件已凍結,則這些物件可以是可共用物件。這表示如果物件已凍結,則不允許變更封裝資料。

(5) 其他

在讓擴充套件具備 Ractor 安全性時,可能還有其他必須考量的重點或需求。此文件將在發現這些重點或需求時加以擴充。