为 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 位。可以使用 FIX2INT() 宏或 FIX2LONG() 将 T_FIXNUM 转换为 C 整数。虽然你必须在使用它们之前检查数据是否真的是 FIXNUM,但它们速度更快。FIX2LONG() 永远不会引发异常,但如果结果大于或小于 int 的大小,则 FIX2INT() 会引发 RangeError
。还有 NUM2INT() 和 NUM2LONG(),它们将任何 Ruby 数字转换为 C 整数。这些宏包含类型检查,因此如果转换失败,将引发异常。NUM2DBL() 可以以相同的方式用于检索 double 浮点值。
你可以使用宏 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
-
左移 1 位,并打开其最低有效位 (LSB)。
- 其他指针值
-
强制转换为 VALUE。
你可以通过检查其 LSB 来确定 VALUE 是否为指针。
注意:Ruby 不允许任意指针值成为 VALUE。它们应该是指向 Ruby 知道的结构的指针。已知的结构定义在 <ruby.h> 中。
要将 C 数字转换为 Ruby 值,请使用这些宏
- INT2FIX()
-
用于 31 位以内的整数。
- INT2NUM()
-
用于任意大小的整数。
如果整数超出 FIXNUM 范围,INT2NUM() 会将其转换为 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 字符串 format 和后续参数追加到 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 定义一个模块函数,它是模块的私有 AND 单例方法。例如,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)
常量定义¶ ↑
我们有 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,则 *name 是 String
。第三个函数采用以 NULL 结尾的 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)
此函数定义由两个环境共享的变量。指向 ‘var’ 的全局变量的值可以通过名为 ‘name’ 的 Ruby 全局变量访问。
可以使用下面的函数定义只读(当然是从 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 世界中的 struct 公开为 Ruby 对象。在这种情况下,利用 `TypedData_XXX` 宏系列,可以将指向 struct 的指针和 Ruby 对象相互转换。
C struct 到 Ruby 对象¶ ↑
可以使用下一个宏将指向 struct 的指针 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 应该如何管理 struct。
`rb_data_type_t` 定义如下。让我们看一下 struct 的每个成员。
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` 是此 struct 实例的标识符。它主要用于收集和发出统计信息。因此,标识符在进程中必须是唯一的,但不需要作为 C 或 Ruby 标识符有效。
这些 `dmark`/`dfree` 函数在 GC
执行期间调用。期间不允许进行对象分配,因此不要在其中分配 ruby 对象。
`dmark` 是一个用于标记从 struct 引用的 Ruby 对象的函数。如果您的 struct 保留了这样的引用,则必须使用 `rb_gc_mark` 或其系列标记 struct 的所有引用。
`dfree` 是一个用于释放指针分配的函数。如果这是 `RUBY_DEFAULT_FREE`,则指针将被直接释放。
`dsize` 以字节为单位计算 struct 的内存消耗。它的参数是指向 struct 的指针。如果难以实现这样的函数,则可以传递 0 作为 `dsize`。但仍然建议避免使用 0。
当发生内存压缩时,会调用 `dcompact`。通过 `rb_gc_mark_movable()` 标记的引用的 Ruby 对象可以在此处通过 `rb_gc_location()` 进行更新。
您必须将 reserved 填充为 0。
parent 可以指向 Ruby 对象继承的另一个 C 类型定义。然后 `TypedData_Get_Struct()` 也接受派生对象。
您可以使用任意值填充 “data” 以供您使用。Ruby 不会对该成员执行任何操作。
flags 是以下标志值的按位或。由于它们需要深入了解 Ruby 中的垃圾收集器,因此如果您不确定,可以将 flags 设置为 0。
- RUBY_TYPED_FREE_IMMEDIATELY
-
此标志使垃圾收集器在需要释放 struct 时立即在
GC
期间调用 `dfree()`。如果 `dfree` 永远不会解锁 Ruby 的内部锁 (GVL),则可以指定此标志。如果未设置此标志,则 Ruby 会延迟调用 `dfree()`,并在与终结器相同的时间调用 `dfree()`。
- RUBY_TYPED_WB_PROTECTED
-
它表明对象的实现支持写屏障。如果设置了此标志,则 Ruby 可以更好地执行对象的垃圾回收。
但是,当设置它时,您有责任在对象的所有方法实现中适当地放置写屏障。否则,Ruby 在运行时可能会崩溃。
有关写屏障的更多信息,请参见 Generational GC。
- RUBY_TYPED_FROZEN_SHAREABLE
-
此标志表示如果对象被冻结,则该对象是可共享的对象。请参阅 Ractor 支持以了解更多详细信息。
如果未设置此标志,则该对象无法通过
Ractor.make_shareable()
方法成为可共享对象。
请注意,此宏可能会引发异常。如果要包装的 sval 持有需要释放的资源(例如,分配的内存、来自外部库的句柄等),则必须使用 rb_protect。
您可以在一个步骤中分配和包装结构,以更可取的方式。
TypedData_Make_Struct(klass, type, data_type, sval)
此宏返回一个分配的 T_DATA 对象,包装指向结构的指针,该指针也被分配。此宏的工作方式类似于:
(sval = ZALLOC(type), TypedData_Wrap_Struct(klass, data_type, sval))
但是,如果它只是简单地分配,则应该使用此宏而不是像上面的代码那样“分配然后包装”,因为后者可能会引发 NoMemoryError
,并且在这种情况下 sval 将会内存泄漏。
klass 和 data_type 参数的工作方式与 `TypedData_Wrap_Struct()` 中的对应参数相同。指向已分配结构的指针将分配给 sval,sval 应该是指定类型的指针。
声明式标记/压缩结构引用¶ ↑
如果您的 struct 引用的是简单的值(而不是封装在条件逻辑或复杂数据结构中的)Ruby 对象,则可以通过声明 struct 中 VALUE 的偏移量引用来提供一种标记和引用更新的替代方法。
这样做允许 Ruby GC
支持标记这些引用,并且允许 GC
在不需要定义 `dmark` 和 `dcompact` 回调的情况下进行压缩。
您必须定义一个 VALUE 指针的静态列表,该列表指向 struct 中引用所在的位置,并将 “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 struct¶ ↑
要从 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。
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
-
从关键字自动生成
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
-
自动生成
多语言化¶ ↑
- 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)
-
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)
-
具有 #to_str 的
Object
-> 指向没有 NUL 字节的String
数据的指针。 保证结果数据以 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
为负数,则为 -optional
- 1)是可选的。如果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 将打包为
Array
,而产生的值可以通过第三/第四个参数的 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 作为块提供。将使用参数 arg1 调用 func1。func2 接收来自 yield 的值作为第一个参数,arg2 作为第二个参数。
在 1.9 中使用 rb_iterate 时,func1 必须调用一些 Ruby 级别的方法。此函数自 1.9 起已过时;请改用 rb_block_call。
- VALUE rb_yield(VALUE val)
-
将 val 作为单个参数传递给块。
- VALUE rb_yield_values(int n, …)
-
使用每个 Ruby 参数一个 C 参数,将
n
个参数传递给块。 - 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` 为真,则根据类似 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, …)
-
立即终止解释器。此函数应在解释器中的 bug 导致的情况下调用。不会进行异常处理,也不会执行 `ensure` 代码块。
注意:在格式字符串中,“%”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` 中设置,则返回真,否则返回假。类似于 `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` 应该是以下值的按位或:
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.7 中引入的 `*_kw` 函数时,可以在支持 Ruby 2.6(或更低版本)的扩展中使用。
#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):只有少数引用不会产生性能影响。
插入写屏障是一个非常困难的 hack,很容易引入严重错误。并且插入写屏障会产生一些开销。基本上我们不建议您插入写屏障。请仔细考虑风险。
与内置类型结合使用¶ ↑
请考虑利用内置类型。大多数内置类型都支持写屏障,因此您可以使用它们来避免手动插入写屏障。
例如,如果您的 T_DATA 具有对其他对象的引用,那么您可以将这些引用移动到 Array
。一个 T_DATA 对象只有一个对数组对象的引用。或者您也可以使用 Struct
对象来收集一个 T_DATA 对象(没有任何引用),以及一个包含引用的 Array
。
使用这种技术,您不再需要插入写屏障。
插入写屏障¶ ↑
[再次强调] 插入写屏障是一个非常困难的 hack,很容易引入严重错误。并且插入写屏障会产生一些开销。基本上我们不建议您插入写屏障。请仔细考虑风险。
在插入写屏障之前,您需要了解 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 来确保在第二次调用 rb_str_new_cstr 时 sptr 的内容保持有效。
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 *之后*。将 RB_GC_GUARD 放在解引用 sptr 之前将毫无用处。RB_GC_GUARD 仅对 VALUE 数据类型有效,而不是转换后的 C 数据类型。
如果在解引用 sptr 之后对 ‘s’ VALUE 进行非内联函数调用,则在上面的示例中完全不需要 RB_GC_GUARD。因此,在上面的示例中,对 ‘s’ 调用任何未内联的函数,例如
rb_str_modify(s);
将确保 ‘s’ 保留在堆栈或寄存器上,以防止 GC
调用过早释放它。
在 C 中使用 RB_GC_GUARD 宏比使用“volatile”关键字更好。RB_GC_GUARD 具有以下优点
-
宏的使用意图很明确
-
RB_GC_GUARD 仅影响其调用站点,“volatile” 每次使用该变量时都会生成一些额外的代码,从而损害优化。
-
“volatile” 的实现在某些编译器和体系结构中可能存在错误/不一致。RB_GC_GUARD 可针对损坏的系统/编译器进行自定义,而不会对其他系统产生负面影响。
附录 F. Ractor
支持¶ ↑
Ractor 是 Ruby 3.0 中引入的并行执行机制。所有 ractor 都可以使用底层系统提供的线程在不同的 OS 线程上并行运行,因此 C 扩展应该是线程安全的。可以在多个 ractor 中运行的 C 扩展称为“Ractor 安全”。
围绕 C 扩展的 Ractor
安全具有以下属性
-
默认情况下,所有 C 扩展都被识别为 Ractor 不安全。
-
Ractor 不安全的 C 方法只能从主
Ractor
调用。如果由非主Ractor
调用,则会引发Ractor::UnsafeError
。 -
如果扩展想要标记为 Ractor 安全,则该扩展应在扩展的 Init_ 函数中调用 rb_ext_ractor_safe(true),并且所有定义的方法都将标记为 Ractor 安全。
要使 C 扩展“Ractor 安全”,我们需要检查以下几点
-
不要在 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 的全局变量中。
-
检查扩展的线程安全性
扩展应该是线程安全的。例如,以下代码不是线程安全的
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 安全比使代码通常线程安全更容易。例如,我们不需要锁定数组对象来访问它的元素。 -
检查任何使用的库的线程安全性
如果扩展依赖于外部库,例如库 libfoo 中的函数 foo(),则函数 libfoo foo() 应该是线程安全的。
-
使对象可共享
这不是使扩展 Ractor 安全所必需的。
如果扩展提供了由 rb_data_type_t 定义的特殊对象,请考虑这些对象是否可以变为可共享对象。
RUBY_TYPED_FROZEN_SHAREABLE 标志表示如果对象被冻结,则这些对象可以成为可共享对象。这意味着如果对象被冻结,则不允许修改包装的数据。
-
其他
在制作 Ractor 安全的扩展时,可能还需要考虑其他要点或要求。本文档将在发现它们时进行扩展。