美文网首页
Createing Extension Libraries fo

Createing Extension Libraries fo

作者: will2yang | 来源:发表于2019-08-13 21:19 被阅读0次

阅读完rubygems.org上对gem extensions的介绍,然后阅读github上ruby项目上对扩展的介绍并翻译自。

这个文档如何为ruby写扩展。

Basic Knowledge

在c语言里,变量有类型但数据没有类型。相对而言,ruby变量没有固定的类型,但是数据本身有类型,所以数据需要在语言之间被转换。
数据在ruby表示为c类型VALUE.每一个VALUE数据有它自己的类型。
为了根据VALUE检索c数据的类型,你需要:
1.标识VALUE的数据类型
2.转换VALUE在C数据
转换错误的数据类型将会导致严重的问题。

Data Types

ruby解释器有以下的数据类型

TYPE DESCRIPTION
T_NIL nil
T_OBJECT ordinary object
T_CLASS class
T_MODULE module
T_FLOAT floating point number
T_STRING string
T_REGEXP regular expression
T_ARRAY array
T_HASH associative array
T_STRUCT (Ruby) structure
T_BIGNUM multi precision integer
T_FIXNUM Fixnum(31bit or 63bit)
T_COMPLEX complex number
T_RATIONAL rational number
T_FILE IO
T_TURE true
T_FALSE false
T_DATA data
T_SYMBOL symbol

除此之外,还有其他几个内部使用的类型

TYPE DESCRIPTION
T_ICLASS included module
T_MATCH MatchData object
T_UNDEF undefined
T_NODE syntax tree node
T_ZOMBIE object awaiting finalization

大部分类型都表示着c语言结构体
Check Data Type of the VALUE
TYPE()宏定义在ruby.h展示了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没有指定的类型,将会抛出一个异常。
这里也有更方便的宏用来检查 fixnums 和 nil

FIXNUM_P(obj)
NIL_P(obj)
Convert VALUE int C Data

数据类型T_NIL,T_FALSE,T_TRUE分别是nil,false,true.他们是数据类型的单例。等同的C常量是:Qnil,Qfalse,Qtrue., Qfalse就是c里的false,但是不是Qnil.
T_FIXNUM是31bit,如果长度是64bit那么T_FIXNUM是63bit.T_FIXNUM可以被转化成Cinteger使用FIX2INT()或FIX2LONG()。虽然在使用它们之前必须检查数据是否真的是FIXNUM,但它们更快。FIX2LONG不会抛出异常,但是FIX2INT()抛出RangeError如果结果超过int的范围。这里也有NUM2INT()和NUM2LONG()用于转换ruby里的number到c的integer。这些函数包含了类型检查,所以转化失败就会抛出异常。NUM2DBL()被用于找回double float值通过相同的方式。
你可以使用StringValue() 和 StringValuePtr()得到一个char*来自一个VALUE().StringValue(var)替换var的值通过var.to_str()的结果。StringValuePtr(var)通过相同的替换然后返回var的指针。在var是一个str的情况下这些函数会跳过。请注意,宏只将左值作为参数,以便更改var的值。
你也可以使用StringValueCStr()函数。更StringValuePtr()很相似,但是加了个一个NUL字符在结果的后面。如果结果包含了NUL字符,这个函数会导致ArgumentError异常。
StringValuePtr不会保证存在NUL在结果后面,但是结果也许包含NUL。
其他数据类型具有相应的C结构,例如,用于T_ARRAY的struct RArray等。可以强制转换具有相应结构的类型的VALUE以检索指向struct的指针。对于每种数据类型,转换宏的形式为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)'访问。
注意:除非您对结果负责,否则不要直接更改结构的值。这最终成为有趣的错误的原因。

Convert C Data into VALUE

转化C数据为Ruby的值。

FIXNUM left shift 1 bit, and turn on its lease significant bit(LSB)
Other pointer values cast to VALUE

您可以通过检查其LSB来确定VALUE是否为指针。

注意:Ruby不允许任意指针值为VALUE。它们应该是Ruby知道的结构的指针。已知结构在<ruby.h>中定义。
使用如下函数把C语言的numbers转成Ruby的值:

INT2FIX() for integers within 31 bits
INT2NUM() for arbitrary sized integers

如果integer超出FIXNUM范围,INT2NUM将它转化一个Bignum,但是速度稍慢。

Manipulating Ruby Data

正如我已经提到的,不建议修改对象的内部结构。要操作对象,请使用Ruby解释器提供的函数。下面列出了一些(不是全部)有用的功能:

String Functions
rb_str_new(const char *ptr, long len) Creates a new Ruby string.
rb_str_new2(const char *ptr)
rb_str_new_cstr(const char *ptr) Creates a new Ruby string from a C string. This is equivalent to rb_str_new(ptr, strlen(ptr)).
rb_str_new_literal(const char *ptr) Creates a new Ruby string from a C string literal.
rb_tainted_str_new(const char *ptr, long len) Creates a new tainted Ruby string. Strings from external data sources should be tainted.
rb_tainted_str_new2(const char *ptr)
rb_tainted_str_new_cstr(const char *ptr) Creates a new tainted Ruby string from a C string.
rb_sprintf(const char *format, …)
rb_vsprintf(const char *format, va_list ap) Creates a new Ruby string with printf(3) format.Note: In the format string, “%”PRIsVALUE can be used for Object#to_s (or Object#inspect if '+' flag is set) output (and related argument must be a VALUE). Since it conflicts with “%i”, for integers in format strings, use “%d”.
rb_str_append(VALUE str1, VALUE str2) Appends Ruby string str2 to Ruby string str1.
rb_str_cat(VALUE str, const char *ptr, long len) Appends len bytes of data from ptr to the Ruby string.
rb_str_cat2(VALUE str, const char* ptr)
rb_str_cat_cstr(VALUE str, const char* ptr) Appends C string ptr to Ruby string str. This function is equivalent to 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) Appends C string format and successive arguments to Ruby string str according to a printf-like format. These functions are equivalent to rb_str_append(str, rb_sprintf(format, …)) and rb_str_append(str, rb_vsprintf(format, ap)), respectively.
rb_enc_str_new(const char *ptr, long len, rb_encoding *enc)
rb_enc_str_new_cstr(const char *ptr, rb_encoding *enc) Creates a new Ruby string with the specified encoding.
rb_enc_str_new_literal(const char *ptr, rb_encoding *enc) Creates a new Ruby string from a C string literal with the specified encoding.
rb_usascii_str_new(const char *ptr, long len)
rb_usascii_str_new_cstr(const char *ptr) Creates a new Ruby string with encoding US-ASCII.
rb_usascii_str_new_literal(const char *ptr) Creates a new Ruby string from a C string literal with encoding US-ASCII.
rb_utf8_str_new(const char *ptr, long len)
rb_utf8_str_new_cstr(const char *ptr) Creates a new Ruby string with encoding UTF-8.
rb_utf8_str_new_literal(const char *ptr) Creates a new Ruby string from a C string literal with encoding UTF-8.
rb_str_resize(VALUE str, long len) Resizes a Ruby string to len bytes. If str is not modifiable, this function raises an exception. The length of str must be set in advance. If len is less than the old length the content beyond len bytes is discarded, else if len is greater than the old length the content beyond the old length bytes will not be preserved but will be garbage. Note that RSTRING_PTR(str) may change by calling this function.
rb_str_set_len(VALUE str, long len) Sets the length of a Ruby string. If str is not modifiable, this function raises an exception. This function preserves the content up to len bytes, regardless RSTRING_LEN(str). len must not exceed the capacity of str.
rb_str_modify(VALUE str) Prepares a Ruby string to modify. If str is not modifiable, this function raises an exception, or if the buffer of str is shared, this function allocates new buffer to make it unshared. Always you MUST call this function before modifying the contents using RSTRING_PTR and/or rb_str_set_len.
Array Functions
rb_ary_new() Creates an array with no elements.
rb_ary_new2(long len)
rb_ary_new_capa(long len) Creates an array with no elements, allocating internal buffer for len elements.
rb_ary_new3(long n, …)
rb_ary_new_from_args(long n, …) Creates an n-element array from the arguments.
rb_ary_new4(long n, VALUE *elts)
rb_ary_new_from_values(long n, VALUE *elts) Creates an n-element array from a C array.
rb_ary_to_ary(VALUE obj) Converts the object into an array. Equivalent to Object#to_ary.

操作阵列有许多功能。如果给出其他类型,它们可能会转储核心。

rb_ary_aref(int argc, const VALUE *argv, VALUE ary) Equivalent to Array#[].
rb_ary_entry(VALUE ary, long offset) ary
rb_ary_store(VALUE ary, long offset, VALUE obj) ary = 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) Appends len elements of objects from ptr to the array.
Extending Ruby with C

你可以添加一些新的功能(class, methods)给ruby解释器。ruby提供了api给下列东西:

  • Classes, Modules
  • Methods, Singleton Methods
  • Constants
    Class和Module的定义
    定义一个class或module,使用如下函数:
VALUE rb_define_class(const char *name, VALUE super)
VALUE rb_define_module(const char *name)

这些函数将会返回新被撞见的class或module。你也许想要保存这些引用用于之后的操作。你可以通过如下的函数去定义嵌套的class 或 module函数:

VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
VALUE rb_define_module_under(VALUE outer, const char *name)
Method and Singleton Method Definition

定义方法或单例方法,使用如下函数:

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)

这里是两个函数定义private/protected methods:

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 定义了一个模块函数,一个private且singleton的模块方法。举个例子,sqrt是一个模块方法定义在Math module。可以通过如下方式调用:

Math.sqrt(4)

include Math
sqrt(4)

用如下方式定义模块方法:

void rb_define_module_function(VALUE module, const char *name,
                               VALUE (*func)(ANYARGS), int argc)

除此之外,function-like 方法,一些被定义在kernel module的方法,通过如下定义:

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);

给一个属性定义一个reader/writer方法,

void rb_define_attr(VALUE klass, const char *name, int read, int write)

定义、取消定义未分配的类方法,

void rb_define_alloc_func(VALUE klass, VALUE (*func)(VALUE klass));
void rb_undef_alloc_func(VALUE klass);

func必须将klass作为参数返回一个新的分配的实例。这个实例尽可能为空,没有包含昂贵的(including external)资源。
如果你要覆盖一个已存在的方法对于class的任何ancestor,你可以依靠:

VALUE rb_call_super(int argc, const VALUE *argv)

为了归档当前作用域的接受者(如果没有其他可用的方式),你可以:

VALUE rb_current_receiver(void)

Constant Definition
我们有两个函数定义常量:

void rb_define_const(VALUE klass, const char *name, VALUE val)
void rb_define_global_const(const char *name, VALUE val)

前者用于定义一个指定class/module的常量,后者为了定义一个全局常量。

Use Ruby Features from C

这里有几种方式在c语言里调用ruby的功能。

Evaluate Ruby Programs in a String

最简单的方式调用Ruby功能在一个C程序里,就是执行string作为ruby程序。这个函数为完成这项工作:

VALUE rb_eval_string(const char *str)

evaluation在当前上下文中完成,因此可以访问最内层方法(由Ruby定义)的当前局部变量。
注意evaluation会raise一个异常。这是一个更安全的函数:

VALUE rb_eval_string_protect(const char *str, int *state)

它会返回nil在错误发生的时候。*state是0如果成功调用,否则是非0.

ID or Symbol

你可以直接调用方法,不需要转换string。首先我们需要解释ID。ID是一个整数表示了ruby的表示就像变量名。与ID对应的ruby类型时Symbol。可以被这样访问在ruby里:

:Identifier

:"any kind of string"

你可以获得ID的值通过C代码:

rb_intern(const char *name)
rb_intern_str(VALUE name)

你可以检索到ID通过Ruby Object作为参数:

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

这些函数和以上的函数相似,但是它们返回的是Symbol而不是ID。

VALUE ID2SYM(ID id)

转换Ruby Symbol对象通过使用

ID SYM2ID(VALUE symbol)
Invoke Ruby Method from C

为了直接调用方法,你可以使用如下函数:

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

此函数调用recv上的方法,方法名称由符号mid指定。

Accessing the Variables and Constants

你可以使用access函数访问类和实力变量。全局变量将会被共享在两个环境。但无法访问Ruby的局部变量。
下列函数能access/modify实力变量:

VALUE rb_ivar_get(VALUE obj, ID id)
VALUE rb_ivar_set(VALUE obj, ID id, VALUE val)

id必须是symbol,能被rb_intern()检索,
访问class/module常量

VALUE rb_const_get(VALUE obj, ID id)

另见上面的常量定义。

Information Sharing Between Ruby and C
Ruby constants That Can Be Accessed From C

如1.3节所述,可以从C引用以下Ruby常量。

Qtrue
QFalse Boolean values, Qfalse is false in C also
Qnil Ruby nil in C scope
Global Variables Shared Between C and Ruby

使用全局变量在两个环境之间信息是可以被共享的。为了定义他们,你可以使用如下函数

void rb_define_variable(const char *name, VALUE *var)

这个函数定义了被共享在两个环境的变量。可以通过名为name'的Ruby全局变量访问var'指向的全局变量的值。
你可以定义read-only的变量使用如下的函数。

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);

你也可以定义一个ruby的全局变量没有对应的c变量。这个变量的值将set/get只通过钩子。

void rb_define_virtual_variable(const char *name,
                                VALUE (*getter)(), void (*setter)())

getter和setter的函数原型分别是如下:

VALUE (*getter)(ID id);
void (*setter)(VALUE val, ID id);
Encapsulate C Data into a Ruby Object

有些时候你需要暴露你的struct在C世界作为一个Ruby Object,情况如下,利用TypedData_XXX宏系列,可以相互转换指向struct和Ruby对象的指针。

  • 旧的(非类型)Data_XXX宏系列已被弃用。在Ruby的未来版本中,旧宏可能无法工作。 ++
C struct to Ruby object

您可以使用下一个宏将sval(指向结构的指针)转换为Ruby对象。

TypedData_Wrap_Struct(klass, data_type, sval)

TypedData_Wrap_Struct()将创建的Ruby对象作为VALUE返回。
klass参数是对象的类。 data_type是指向const rb_data_type_t的指针,它描述了Ruby应该如何管理结构。

建议klass派生自一个名为Data(rb_cData)的特殊类,但不是来自Object或其他序数类。如果没有,则必须调用rb_undef_alloc_func(klass)。

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 *reserved[2];
        } function;
        const rb_data_type_t *parent;
        void *data;
        VALUE flags;
};

wrap_struct_name是此struct实例的标识符。它主要用于收集和发布统计数据。因此,标识符在进程中必须是唯一的,但不需要作为C或Ruby标识符有效。

在GC执行期间调用这些dmark / dfree函数。在此期间不允许任何对象分配,因此不要在其中分配ruby对象。

dmark是一个标记从结构引用的Ruby对象的函数。如果你的结构保留了这样的引用,它必须使用rb_gc_mark或其族标记结构中的所有引用。

  • 请注意,建议避免此类引用。 ++

dfree是一个释放指针分配的函数。如果这是-1,则指针将被释放。

dsize通过struct计算内存消耗(以字节为单位)。它的参数是指向struct的指针。如果很难实现这样的功能,你可以将0作为dsize传递。但仍建议避免使用0。

你必须用0填充保留和父。

您可以使用任意值填充“数据”供您使用。 Ruby对该成员没有任何作用。

flags是以下标志值的按位或。由于它们需要深入理解Ruby中的垃圾收集器,因此如果您不确定,可以将0设置为标志。

RUBY_TYPED_FREE_IMMEDIATELY This flag makes the garbage collector immediately invoke dfree() during GC when it need to free your struct. You can specify this flag if the dfree never unlocks Ruby's internal lock (GVL).If this flag is not set, Ruby defers invokation of dfree() and invokes dfree() at the same time as finalizers.
RUBY_TYPED_WB_PROTECTED It shows that implementation of the object supports write barriers. If this flag is set, Ruby is better able to do garbage collection of the object. When it is set, however, you are responsible for putting write barriers in all implementations of methods of that object as appropriate. Otherwise Ruby might crash while running.More about write barriers can be found in “Generational GC” in Appendix D.

你可以通过一个步骤分配和包裹结构体

TypedData_Make_Struct(klass, type, data_type, sval)

此宏返回一个已分配的Data对象,将指针包装到结构中,该结构也已分配。这个宏的作用如下:

(sval = ZALLOC(type), TypedData_Wrap_Struct(klass, data_type, sval))

参数klass和data_type的工作方式与TypedData_Wrap_Struct()中的对应项类似。指向已分配结构的指针将分配给sval,该sval应该是指定类型的指针。

Ruby object to C struct

要从Data对象检索C指针,请使用宏TypedData_Get_Struct()。

TypedData_Get_Struct(obj, type, &data_type, sval)

指向结构的指针将分配给变量sval。
有关详细信息,请参阅以下示例

相关文章

网友评论

      本文标题:Createing Extension Libraries fo

      本文链接:https://www.haomeiwen.com/subject/rzezdctx.html