为 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() 可以以相同的方式用于检索双精度浮点值。
可以使用宏 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 定义一个模块函数,它是该模块的私有 **且** 单例方法。例如,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,则它为 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)
此函数定义由两个环境共享的变量。可以通过名为“name”的 Ruby 全局变量访问“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() 更新。
您必须用 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 在运行时可能会崩溃。
有关写屏障的更多信息,请参见分代 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,它应该是指针指定的类型。
声明式地标记/压缩结构引用¶ ↑
如果您的结构体引用的是简单值(而不是包装在条件逻辑或复杂数据结构中)的 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 将 dbm 结构包装在 C 环境中。
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
安装。应该按以下方式包含rbimpl_
或 RBIMPL_
开头的符号外,所有符号都是公共 API。它们是实现细节,不应被 C 扩展使用。
仅允许 C 扩展包含 $repo_root/include/ruby/*.h
,其对应的宏定义在 $repo_root/include/ruby.h
标头中。
$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
-
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
-
自动生成
多语言化¶ ↑
- 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 对象(默认真值)
- 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 <-> integer
- FIX2LONG(value), LONG2FIX(l)
-
Fixnum <-> long
- NUM2INT(value), INT2NUM(i)
-
Numeric
<-> integer - NUM2UINT(value), UINT2NUM(ui)
-
Numeric
<-> unsigned integer - NUM2LONG(value), LONG2NUM(l)
-
Numeric
<-> long - NUM2ULONG(value), ULONG2NUM(ul)
-
Numeric
<-> unsigned long - NUM2LL(value), LL2NUM(ll)
-
Numeric
<-> long long - NUM2ULL(value), ULL2NUM(ull)
-
Numeric
<-> unsigned long long - 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)
-
具有 #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
(如果optional
为负数,则为 -optional
- 1)个 ID 是可选的。如果keyword_hash
中不包含强制性键,则会引发 “missing keyword”ArgumentError
。如果keyword_hash
中不存在可选键,则values
中的相应元素设置为Qundef
。如果optional
为负数,则忽略keyword_hash
的其余部分,否则会引发 “unknown keyword”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 指定,带有 argv 中的 argc 个参数,并将 func 作为块提供。当 func 作为块调用时,它将接收来自 yield 的值作为第一个参数,data2 作为第二个参数。当使用多个值(在 C 中,rb_yield_values()、rb_yield_values2() 和 rb_yield_splat())yield 时,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 作为块提供。func1 将使用参数 arg1 调用。func2 接收来自 yield 的值作为第一个参数,arg2 作为第二个参数。
当在 1.9 中使用 rb_iterate 时,func1 必须调用一些 Ruby 级别的方法。自 1.9 以来,此函数已过时;请改用 rb_block_call。
- VALUE rb_yield(VALUE val)
-
将 val 作为单个参数 yield 到块。
- VALUE rb_yield_values(int n, …)
-
将
n
个参数 yield 到块,每个 Ruby 参数使用一个 C 参数。 - VALUE rb_yield_values2(int n, VALUE *argv)
-
将
n
个参数 yield 到块,所有 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(全局虚拟机锁),它阻止 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 变为可读状态,允许调度其他线程。如果可以执行读取操作,则返回 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)
-
允许使用指定的超时时间等待单个 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(),它从
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。
与这些宏对应的头文件可以直接从扩展库中包含。
- 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 中可用的函数¶ ↑
请参阅 mkmf 的文档。
附录 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 来确保在第二次调用 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 都可以使用底层系统提供的线程在不同的操作系统线程上并行运行,因此 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 安全扩展时,可能还有其他要点或要求需要考虑。本文档将在发现后进行扩展。