为 Ruby 创建扩展库

本文档解释了如何为 Ruby 创建扩展库。

基础知识

在 C 中,变量有类型,而数据没有类型。相反,Ruby 变量没有静态类型,数据本身有类型,因此需要在语言之间转换数据。

Ruby 中的对象由 C 类型 'VALUE' 表示。每个 VALUE 数据都有其数据类型。

要从 VALUE 中检索 C 数据,你需要

  1. 识别 VALUE 的数据类型

  2. 将 VALUE 转换为 C 数据

转换为错误的数据类型可能会导致严重的问题。

Ruby 数据类型

Ruby 解释器具有以下数据类型

T_NIL

nil

T_OBJECT

普通对象

T_CLASS

T_MODULE

模块

T_FLOAT

浮点数

T_STRING

字符串

T_REGEXP

正则表达式

T_ARRAY

数组

T_HASH

关联数组

T_STRUCT

(Ruby) 结构体

T_BIGNUM

多精度整数

T_FIXNUM

Fixnum(31 位或 63 位整数)

T_COMPLEX

复数

T_RATIONAL

有理数

T_FILE

IO

T_TRUE

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

ClassModule 定义

要定义类或模块,请使用以下函数

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 对象(SymbolString)中检索 ID:

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

如果参数不是 SymbolString,则这些函数会尝试将参数转换为 String。如果字符串不是已知的符号,则第二个函数会将转换后的结果存储到 *name 中,并返回 0。在此函数返回非零值后,*name 始终是 SymbolString,否则,如果结果为 0,则 *name 是 String。第三个函数采用以 NULL 结尾的 C 字符串,而不是 Ruby VALUE。

您可以使用以下方法从作为参数给出的 Ruby 对象(SymbolString)中检索 Symbol

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

这些函数与上述函数类似,只是这些函数会返回 Symbol,而不是 ID。

您可以使用以下方法将 C ID 转换为 Ruby Symbol

VALUE ID2SYM(ID id)

要将 Ruby Symbol 对象转换为 ID,请使用:

ID SYM2ID(VALUE symbol)

从 C 中调用 Ruby 方法

要直接调用方法,可以使用以下函数:

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

此函数会在 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` 结尾。

提供了一些宏来简化边缘引用:

下面的示例来自 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_cppflagsappend_cpflagsappend_ldflags,而不是直接附加上述变量。

通常,对象文件列表是通过搜索源文件自动生成的,但是如果任何源文件在构建时生成,则必须显式定义它们。

如果未满足编译条件,则不应调用 “create_makefile”。 Makefile 将不会生成,编译将不会完成。

准备 depend(可选)

如果存在名为 depend 的文件,则 Makefile 将包含该文件以检查依赖项。 你可以通过调用来创建此文件

% gcc -MM *.c > depend

这是无害的。 准备它。

生成 Makefile

尝试通过以下方式生成 Makefile

ruby extconf.rb

如果该库应安装在 vendor_ruby 目录而不是 site_ruby 目录下,请使用 –vendor 选项,如下所示。

ruby extconf.rb --vendor

如果将扩展库放在 ruby 源代码树的 ext 目录下,则不需要此步骤。 在这种情况下,解释器的编译将为你完成此步骤。

运行 make

键入

make

来编译你的扩展。 如果你已将扩展库放在 ruby 源代码树的 ext 目录下,则也不需要此步骤。

调试

你可能需要 rb_debug 扩展。 可以通过在 ext/Setup 文件中添加目录名称来静态链接扩展,以便你可以使用调试器检查扩展。

完成! 现在你有了扩展库

你可以对你的库执行任何操作。 Ruby 的作者不会对你依赖 Ruby API 的代码声明任何限制。 可以随意使用、修改、分发或出售你的程序。

附录 A. Ruby 头文件和源文件概述

Ruby 头文件

$repo_root/include/ruby 下的所有内容都与 make install 一起安装。 应该通过 C 扩展中的 #include <ruby.h> 包含它。 所有符号都是公共 API,但以 rbimpl_RBIMPL_ 为前缀的符号除外。 它们是实现细节,C 扩展不应使用它们。

只有在 $repo_root/include/ruby.h 头文件中定义了相应宏的 $repo_root/include/ruby/*.h 才能被 C 扩展 #include

$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

Fiber, Continuation

dir.c

目录

enum.c

可枚举

enumerator.c

枚举器

file.c

文件

hash.c

哈希

io.c

IO

marshal.c

封送

math.c

数学

numeric.c

Numeric, Integer, Fixnum, Float

pack.c

Array#pack, String#unpack

proc.c

Binding, Proc

process.c

进程

random.c

随机数

range.c

范围

rational.c

有理数

re.c

Regexp, MatchData

signal.c

信号

sprintf.c

String#sprintf

string.c

字符串

struct.c

结构体

time.c

时间

defs/known_errors.def

Errno::* 异常类

-> known_errors.inc

自动生成

多语言化

encoding.c

编码

transcode.c

Encoding::Converter

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)

String -> String 数据长度(以字节为单位)

RSTRING_PTR(str)

String -> 指向 String 数据的指针。 请注意,结果指针可能不是以 NUL 结尾的

StringValue(value)

具有 #to_str 的 Object -> String

StringValuePtr(value)

具有 #to_str 的 Object -> 指向 String 数据的指针

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 具有以下优点

  1. 宏的使用意图很明确

  2. RB_GC_GUARD 仅影响其调用站点,“volatile” 每次使用该变量时都会生成一些额外的代码,从而损害优化。

  3. “volatile” 的实现在某些编译器和体系结构中可能存在错误/不一致。RB_GC_GUARD 可针对损坏的系统/编译器进行自定义,而不会对其他系统产生负面影响。

附录 F. Ractor 支持

Ractor 是 Ruby 3.0 中引入的并行执行机制。所有 ractor 都可以使用底层系统提供的线程在不同的 OS 线程上并行运行,因此 C 扩展应该是线程安全的。可以在多个 ractor 中运行的 C 扩展称为“Ractor 安全”。

围绕 C 扩展的 Ractor 安全具有以下属性

  1. 默认情况下,所有 C 扩展都被识别为 Ractor 不安全。

  2. Ractor 不安全的 C 方法只能从主 Ractor 调用。如果由非主 Ractor 调用,则会引发 Ractor::UnsafeError

  3. 如果扩展想要标记为 Ractor 安全,则该扩展应在扩展的 Init_ 函数中调用 rb_ext_ractor_safe(true),并且所有定义的方法都将标记为 Ractor 安全。

要使 C 扩展“Ractor 安全”,我们需要检查以下几点

  1. 不要在 ractor 之间共享不可共享的对象

    例如,C 的全局变量可能导致在 ractor 之间共享不可共享的对象。

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

    set() 和 get() 对可以使用 g_var 共享不可共享的对象,并且它是 Ractor 不安全的。

    不仅直接使用全局变量,一些间接数据结构(例如全局 st_table)也可以共享对象,因此请注意。

    请注意,类和模块对象是可共享对象,因此您可以将代码 “cFoo = rb_define_class(…)” 保留在 C 的全局变量中。

  2. 检查扩展的线程安全性

    扩展应该是线程安全的。例如,以下代码不是线程安全的

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

    因为 g_called 全局变量应该由其他 ractor 的线程同步。为了避免这种数据竞争,应该使用一些同步。请检查 include/ruby/thread_native.h 和 include/ruby/atomic.h。

    对于 Ractor,作为方法参数给出的所有对象和接收者 (self) 都保证来自当前的 Ractor 或可共享。因此,使代码 ractor 安全比使代码通常线程安全更容易。例如,我们不需要锁定数组对象来访问它的元素。

  3. 检查任何使用的库的线程安全性

    如果扩展依赖于外部库,例如库 libfoo 中的函数 foo(),则函数 libfoo foo() 应该是线程安全的。

  4. 使对象可共享

    这不是使扩展 Ractor 安全所必需的。

    如果扩展提供了由 rb_data_type_t 定义的特殊对象,请考虑这些对象是否可以变为可共享对象。

    RUBY_TYPED_FROZEN_SHAREABLE 标志表示如果对象被冻结,则这些对象可以成为可共享对象。这意味着如果对象被冻结,则不允许修改包装的数据。

  5. 其他

    在制作 Ractor 安全的扩展时,可能还需要考虑其他要点或要求。本文档将在发现它们时进行扩展。