为 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
-
true
- T_FALSE
-
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() 永远不会引发异常,但 FIX2INT() 会引发 RangeError
,如果结果大于或小于 int 的大小。还有 NUM2INT() 和 NUM2LONG(),它们将任何 Ruby 数字转换为 C 整数。这些宏包含类型检查,因此如果转换失败,将引发异常。NUM2DBL() 可以用来以相同的方式获取双精度浮点数的值。
您可以使用宏 StringValue() 和 StringValuePtr() 从 VALUE 获取 char*。StringValue(var) 将 var 的值替换为 “var.to_str()” 的结果。StringValuePtr(var) 执行相同的替换并返回 var 的 char* 表示形式。如果 var 是一个 String
,这些宏将跳过替换。请注意,这些宏只接受左值作为参数,以便就地更改 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
-
左移一位,并打开其最低有效位 (LSB)。
- 其他指针值
-
强制转换为 VALUE。
您可以通过检查其 LSB 来确定 VALUE 是否为指针。
注意:Ruby 不允许将任意指针值作为 VALUE。它们应该是指向 Ruby 知道的结构的指针。已知结构在 <ruby.h> 中定义。
要将 C 数字转换为 Ruby 值,请使用以下宏
- INT2FIX()
-
用于 31 位内的整数。
- INT2NUM()
-
用于任意大小的整数。
如果 INT2NUM() 超出 FIXNUM 范围,它会将整数转换为 Bignum,但速度稍慢。
操作 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 修改内容之前调用此函数。
Array
函数¶ ↑
- 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
-
类、模块
-
方法、单例方法
-
常量
Class
和 Module
定义¶ ↑
要定义类或模块,请使用以下函数
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)
Method
和单例方法定义¶ ↑
要定义方法或单例方法,请使用以下函数
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)
要定义和取消定义“allocate”类方法,请使用
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)
常量定义¶ ↑
我们有两个函数用于定义常量
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, ...)
此函数在 recv 上调用一个方法,方法名称由符号 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_mark_movable() 标记的引用的 Ruby 对象可以在此处根据 rb_gc_location() 进行更新。
您必须将 reserved 填充为 0。
parent 可以指向另一个 C 类型定义,Ruby 对象从该定义继承而来。然后 TypedData_Get_Struct() 也接受派生对象。
您可以使用任意值填充“data”,供您使用。Ruby 对该成员不做任何操作。
flags 是以下标志值的按位或。由于它们需要深入了解 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 对象的情况下,提供了一种替代的标记和引用更新方法,方法是声明对结构中 VALUES 的偏移引用。
这样做允许 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
。
$repo_root/internal/
下或直接在根目录 $repo_root/*.h
下的头文件不会被 make 安装。它们是内部头文件,只有内部 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
-
从 keywords 自动生成
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
-
Bignum
- 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
-> 双精度浮点数 - rb_float_new(f)
-
双精度浮点数 ->
Float
- RSTRING_LEN(str)
- RSTRING_PTR(str)
- StringValue(value)
- StringValuePtr(value)
- StringValueCStr(value)
-
Object
with #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 之间共享。如果名称包含不允许作为符号一部分的字符,则 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
(-optional
- 1 如果optional
为负)个 ID 是可选的。如果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)
-
在 recv 上调用一个方法,方法名称由符号 mid 指定,参数数量为 argc,参数列表为 argv,并将 func 作为 block 提供。当 func 作为 block 被调用时,它将接收来自 yield 的值作为第一个参数,并将 data2 作为第二个参数。当使用多个值进行 yield(在 C 中,rb_yield_values()、rb_yield_values2() 和 rb_yield_splat())时,data2 将被打包成一个
Array
,而 yield 的值可以通过第三个/第四个参数的 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 作为 block 提供。func1 将使用参数 arg1 被调用。func2 接收来自 yield 的值作为第一个参数,arg2 作为第二个参数。
当 rb_iterate 在 1.9 中使用时,func1 必须调用一些 Ruby 级别的函数。此函数自 1.9 以来已过时;请使用 rb_block_call 代替。
- VALUE rb_yield(VALUE val)
-
将 val 作为单个参数 yield 给 block。
- VALUE rb_yield_values(int n, …)
-
将
n
个参数 yield 给 block,每个 Ruby 参数使用一个 C 参数。 - VALUE rb_yield_values2(int n, VALUE *argv)
-
将
n
个参数 yield 给 block,所有 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 为真,则根据类似 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, …)
-
立即终止解释器。此函数应在解释器错误导致的情况下调用。不会进行异常处理或确保执行。
注意:在格式字符串中,可以使用“%”PRIsVALUE 用于 Object#to_s
(或如果设置了“+”标志,则使用 Object#inspect
)输出(并且相关参数必须是 VALUE)。由于它与“%i”冲突,因此在格式字符串中,对于整数,请使用“%d”。
线程¶ ↑
从 Ruby 1.9 开始,Ruby 支持原生 1:1 线程,每个 Ruby Thread
对象对应一个内核线程。目前,存在一个 GVL(全局虚拟机锁),它阻止了 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)
-
无限期地等待给定的 FD 变为可读,允许调度其他线程。如果可以执行读取操作,则返回真值;如果存在不可恢复的错误,则返回假值。
- int rb_io_wait_writable(int fd)
-
与 rb_io_wait_readable 相似,但用于可写性。
- int rb_wait_for_single_fd(int fd, int events, struct timeval *timeout)
-
允许在单个 FD 上等待一个或多个事件,并指定超时时间。
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 使用比现代平台上历史限制允许的更大的 FD 集。
- 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,类似于 FD_ZERO(3)。
- void rb_fd_set(int fd, rb_fdset_t *)
-
在 rb_fdset_t 中添加给定的 FD,类似于 FD_SET(3)。
- void rb_fd_clr(int fd, rb_fdset_t *)
-
从 rb_fdset_t 中移除给定的 FD,类似于 FD_CLR(3)。
- int rb_fd_isset(int fd, const rb_fdset_t *)
-
如果给定的 FD 在 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(),该函数从
Regexp
对象创建String
对象。这与 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 中使用的函数¶ ↑
请参阅 MakeMakefile 的文档。
附录 D. 代际 GC
¶ ↑
Ruby 2.1 引入了代际垃圾收集器(称为 RGenGC)。RGenGC(大部分)保持兼容性。
通常,在扩展库中使用称为写屏障的技术对于代际 GC
(en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29) 是必需的。RGenGC 在扩展库中没有写屏障也能正常工作。
如果您的库遵循以下提示,则可以进一步提高性能。特别是,“不要直接触碰指针”部分很重要。
不兼容性¶ ↑
您不能直接写入 RBASIC(obj)->klass 字段,因为它现在是 const 值。
基本上,您不应该写入此字段,因为 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 对象,您可能需要考虑使用写入屏障。
将写入屏障插入 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)也会共享对象,因此请注意。
请注意,类和模块对象是可共享对象,因此您可以使用 C 的全局变量保留代码“cFoo = rb_define_class(…)”。
(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。
使用 Ractor 时,作为方法参数传递的所有对象和接收者(self)都保证来自当前 Ractor
或可共享。因此,使代码 Ractor 安全比使代码普遍线程安全更容易。例如,我们不需要锁定数组对象来访问它的元素。
(3) 检查任何使用库的线程安全性
如果扩展依赖于外部库,例如来自库 libfoo 的函数 foo(),则函数 libfoo foo() 应该线程安全。
(4) 使对象可共享
这对于使扩展 Ractor 安全不是必需的。
如果扩展提供由 rb_data_type_t 定义的特殊对象,请考虑这些对象是否可以成为可共享对象。
RUBY_TYPED_FROZEN_SHAREABLE 标志表示如果对象被冻结,这些对象可以是可共享对象。这意味着如果对象被冻结,则不允许对包装数据的修改。
(5) 其他
在制作 Ractor 安全扩展时,可能还有其他需要考虑的要点或要求。随着这些要点被发现,本文档将被扩展。