為 Ruby 建立擴充函式庫¶ ↑
此文件說明如何為 Ruby 建立擴充函式庫。
基本知識¶ ↑
在 C 中,變數有型別,但資料沒有型別。相反地,Ruby 變數沒有靜態型別,但資料本身有型別,因此資料需要在語言之間轉換。
Ruby 中的物件由 C 型別「VALUE」表示。每個 VALUE 資料都有其資料型別。
若要從 VALUE 擷取 C 資料,您需要
-
識別 VALUE 的資料型別
-
將 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
- 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 物件 (Symbol
或 String
) 擷取 ID
rb_to_id(VALUE symbol) rb_check_id(volatile VALUE *name) rb_check_id_cstr(const char *name, long len, rb_encoding *enc)
如果引數不是 Symbol
或 String
,這些函式會嘗試將引數轉換為 String
。第二個函式會將轉換後的結果儲存在 *name 中,如果字串不是已知的符號,則傳回 0。此函式傳回非零值後,*name 永遠是 Symbol
或 String
,否則,如果結果為 0,則為 String
。第三個函式採用以 NUL 結束的 C 字串,而不是 Ruby VALUE。
您可以使用以下方式從作為引數提供的 Ruby 物件 (Symbol
或 String
) 擷取 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
壓縮,而無需定義 dmark
和 dcompact
回呼。
你必須定義一個靜態 VALUE 指標清單,指向結構中存放參考的偏移,並將「data」成員設定為指向此參考清單。參考清單必須以 RUBY_END_REFS
結尾。
已提供一些巨集,以簡化邊緣參考
-
RUBY_TYPED_DECL_MARKING
= 可設定在ruby_data_type_t
上的旗標,用於表示參考已宣告為邊緣。 -
RUBY_REFERENCES(ref_list_name)
- 定義 ref_list_name 為參考清單 -
RUBY_REF_END
- 參考清單的結束標記。 -
RUBY_REF_EDGE(struct, member)
- 宣告 member 為 struct 的 VALUE 邊緣。在RUBY_REFERENCES_START
後使用此宣告 -
RUBY_REFS_LIST_PTR
- 將參考清單轉換為現有dmark
介面可以接受的格式。
以下範例來自 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_cppflags
、append_cpflags
和 append_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
- dir.c
- enum.c
- enumerator.c
- file.c
- hash.c
- io.c
- marshal.c
- math.c
- numeric.c
- pack.c
- proc.c
- process.c
- random.c
-
亂數
- range.c
- rational.c
- re.c
- signal.c
- sprintf.c
- string.c
- struct.c
- time.c
- defs/known_errors.def
-
Errno::* 例外類別
- -> known_errors.inc
-
自動產生
多語化¶ ↑
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)
-
value
是Integer
嗎? - RB_FLOAT_TYPE_P(value)
-
value
是Float
嗎? - 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)
- RSTRING_PTR(str)
- StringValue(value)
- StringValuePtr(value)
- StringValueCStr(value)
- 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(大多數)保持相容性。
一般來說,世代式GC
(en.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 具有下列優點
-
巨集使用的意圖很清楚
-
RB_GC_GUARD 僅影響其呼叫位置,「volatile」會在每次使用變數時產生一些額外程式碼,損害最佳化。
-
「volatile」實作在某些編譯器和架構中可能是錯誤/不一致的。RB_GC_GUARD 可自訂為損壞的系統/編譯器,而不會對其他系統產生負面影響。
附錄 F. Ractor
支援¶ ↑
Ractor 是 Ruby 3.0 中引入的平行執行機制。所有 Ractor 都可以在不同的作業系統執行緒上平行執行(使用底層系統提供的執行緒),因此 C 擴充功能應具備執行緒安全性。可以在多個 Ractor 中執行的 C 擴充功能稱為「Ractor 安全」。
Ractor
安全性在 C 擴充功能周圍具有下列屬性
-
預設情況下,所有 C 擴充功能都被視為 Ractor 不安全。
-
Ractor 不安全的 C 方法只能從主
Ractor
呼叫。如果由非主Ractor
呼叫,則會引發Ractor::UnsafeError
。 -
如果擴充功能想要標記為 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 安全性時,可能還有其他必須考量的重點或需求。此文件將在發現這些重點或需求時加以擴充。