日本語

Erweiterungsbibliotheken für Ruby erstellen

Dieses Dokument erklärt, wie man Erweiterungsbibliotheken für Ruby erstellt.

Grundkenntnisse

In C haben Variablen Typen und Daten haben keine Typen. Im Gegensatz dazu haben Ruby-Variablen keinen statischen Typ, und Daten selbst haben Typen, sodass Daten zwischen den Sprachen konvertiert werden müssen.

Objekte in Ruby werden durch den C-Typ 'VALUE' repräsentiert. Jede VALUE-Daten hat ihren Datentyp.

Um C-Daten aus einem VALUE abzurufen, müssen Sie

  1. den Datentyp des VALUE identifizieren

  2. das VALUE in C-Daten konvertieren

Die Konvertierung in den falschen Datentyp kann zu schwerwiegenden Problemen führen.

Ruby-Datentypen

Der Ruby-Interpreter hat die folgenden Datentypen

T_NIL

nil

T_OBJECT

gewöhnliches Objekt

T_CLASS

Klasse

T_MODULE

Modul

T_FLOAT

Gleitkommazahl

T_STRING

Zeichenkette

T_REGEXP

regulärer Ausdruck

T_ARRAY

Array

T_HASH

assoziatives Array

T_STRUCT

(Ruby) Struktur

T_BIGNUM

mehrere Genauigkeits-Ganzzahl

T_FIXNUM

Fixnum (31-Bit- oder 63-Bit-Ganzzahl)

T_COMPLEX

komplexe Zahl

T_RATIONAL

rationale Zahl

T_FILE

IO

T_TRUE

wahr

T_FALSE

falsch

T_DATA

Daten

T_SYMBOL

Symbol

Zusätzlich gibt es mehrere andere Typen, die intern verwendet werden

T_ICLASS

eingebundenes Modul

T_MATCH

MatchData Objekt

T_UNDEF

undefiniert

T_NODE

Syntaxbaum-Knoten

T_ZOMBIE

Objekt wartet auf Finalisierung

Die meisten Typen werden durch C-Strukturen repräsentiert.

Typ der VALUE-Daten überprüfen

Das Makro TYPE() in ruby.h zeigt den Datentyp des VALUE an. TYPE() gibt die Konstante T_XXXX zurück, die oben beschrieben wurde. Um Datentypen zu handhaben, wird Ihr Code etwa so aussehen

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

Es gibt die Funktion zur Überprüfung des Datentyps

void Check_Type(VALUE value, int type)

die eine Ausnahme auslöst, wenn das VALUE nicht den angegebenen Typ hat.

Es gibt auch schnellere Prüfmakros für Fixnums und nil.

FIXNUM_P(obj)
NIL_P(obj)

VALUE in C-Daten konvertieren

Die Daten für die Typen T_NIL, T_FALSE, T_TRUE sind nil, false bzw. true. Sie sind Singletons für den Datentyp. Die entsprechenden C-Konstanten sind: Qnil, Qfalse, Qtrue. RTEST() gibt true zurück, wenn ein VALUE weder Qfalse noch Qnil ist. Wenn Sie Qfalse von Qnil unterscheiden müssen, testen Sie speziell gegen Qfalse.

Die T_FIXNUM-Daten sind feste Ganzzahlen mit 31 oder 63 Bit Länge. Diese Größe hängt von der Größe von long ab: wenn long 32 Bit beträgt, ist T_FIXNUM 31 Bit, wenn long 64 Bit beträgt, ist T_FIXNUM 63 Bit. T_FIXNUM kann mit dem Makro FIX2INT() oder FIX2LONG() in eine C-Ganzzahl konvertiert werden. Obwohl Sie überprüfen müssen, dass die Daten wirklich FIXNUM sind, bevor Sie sie verwenden, sind sie schneller. FIX2LONG() löst niemals Ausnahmen aus, aber FIX2INT() löst RangeError aus, wenn das Ergebnis größer oder kleiner als die Größe von int ist. Es gibt auch NUM2INT() und NUM2LONG(), die beliebige Ruby-Zahlen in C-Ganzzahlen konvertieren. Diese Makros enthalten eine Typenprüfung, sodass eine Ausnahme ausgelöst wird, wenn die Konvertierung fehlschlägt. NUM2DBL() kann auf die gleiche Weise verwendet werden, um den Double-Float-Wert abzurufen.

Sie können die Makros StringValue() und StringValuePtr() verwenden, um ein char* aus einem VALUE zu erhalten. StringValue(var) ersetzt den Wert von var durch das Ergebnis von „var.to_str()“. StringValuePtr(var) tut dasselbe und gibt die char*-Darstellung von var zurück. Diese Makros überspringen die Ersetzung, wenn var ein String ist. Beachten Sie, dass die Makros nur das lvalue als Argument nehmen, um den Wert von var an Ort und Stelle zu ändern.

Sie können auch das Makro namens StringValueCStr() verwenden. Dies ist dasselbe wie StringValuePtr(), fügt aber immer ein NUL-Zeichen am Ende des Ergebnisses hinzu. Wenn das Ergebnis ein NUL-Zeichen enthält, löst dieses Makro die ArgumentError Ausnahme aus. StringValuePtr() garantiert nicht die Existenz eines NUL am Ende des Ergebnisses, und das Ergebnis kann NUL enthalten.

Andere Datentypen haben entsprechende C-Strukturen, z. B. struct RArray für T_ARRAY usw. Das VALUE des Typs, der die entsprechende Struktur hat, kann gecastet werden, um den Zeiger auf die Struktur abzurufen. Das Casting-Makro hat die Form RXXXX für jeden Datentyp; zum Beispiel RARRAY(obj). Siehe „ruby.h“. Wir empfehlen jedoch nicht, RXXXX-Daten direkt zuzugreifen, da diese Datenstrukturen komplex sind. Verwenden Sie die entsprechenden rb_xxx()-Funktionen, um auf die interne Struktur zuzugreifen. Um beispielsweise auf einen Eintrag eines Arrays zuzugreifen, verwenden Sie rb_ary_entry(ary, offset) und rb_ary_store(ary, offset, obj).

Es gibt einige Zugriffs-Makros für Strukturmitglieder, zum Beispiel 'RSTRING_LEN(str)', um die Größe des Ruby- String Objekts zu erhalten. Der zugewiesene Bereich kann über 'RSTRING_PTR(str)' zugegriffen werden.

Hinweis: Ändern Sie den Wert der Struktur nicht direkt, es sei denn, Sie sind für das Ergebnis verantwortlich. Dies führt zu interessanten Fehlern.

C-Daten in VALUE konvertieren

Um C-Daten in Ruby-Werte zu konvertieren

FIXNUM

links um 1 Bit verschieben und das niedrigste Bit (LSB) einschalten.

Andere Zeigerwerte

nach VALUE casten.

Sie können feststellen, ob ein VALUE ein Zeiger ist oder nicht, indem Sie seinen LSB überprüfen.

Hinweis: Ruby erlaubt keine beliebigen Zeigerwerte als VALUE. Sie sollten Zeiger auf Strukturen sein, die Ruby kennt. Die bekannten Strukturen sind in <ruby.h> definiert.

Um C-Zahlen in Ruby-Werte zu konvertieren, verwenden Sie diese Makros

INT2FIX()

für Ganzzahlen bis 31 Bit.

INT2NUM()

für Ganzzahlen beliebiger Größe.

INT2NUM() konvertiert eine Ganzzahl in ein Bignum, wenn sie außerhalb des FIXNUM-Bereichs liegt, ist aber etwas langsamer.

Ruby-Objekte manipulieren

Wie bereits erwähnt, wird nicht empfohlen, die interne Struktur eines Objekts zu ändern. Verwenden Sie zur Manipulation von Objekten die vom Ruby-Interpreter bereitgestellten Funktionen. Einige (nicht alle) nützlichen Funktionen sind unten aufgeführt

String-Funktionen

rb_str_new(const char *ptr, long len)

Erstellt eine neue Ruby-Zeichenkette.

rb_str_new2(const char *ptr)
rb_str_new_cstr(const char *ptr)

Erstellt eine neue Ruby-Zeichenkette aus einer C-Zeichenkette. Dies ist äquivalent zu rb_str_new(ptr, strlen(ptr)).

rb_str_new_literal(const char *ptr)

Erstellt eine neue Ruby-Zeichenkette aus einem C-Zeichenketten-Literal.

rb_sprintf(const char *format, …)
rb_vsprintf(const char *format, va_list ap)

Erstellt eine neue Ruby-Zeichenkette mit printf(3)-Format.

Hinweis: In der Formatzeichenkette kann „%“PRIsVALUE für die Ausgabe von Object#to_s (oder Object#inspect, wenn das '+'-Flag gesetzt ist) verwendet werden (und das zugehörige Argument muss ein VALUE sein). Da dies mit „%i“ für Ganzzahlen in Formatzeichenketten kollidiert, verwenden Sie „%d“.

rb_str_append(VALUE str1, VALUE str2)

Hängt die Ruby-Zeichenkette str2 an die Ruby-Zeichenkette str1 an.

rb_str_cat(VALUE str, const char *ptr, long len)

Hängt len Bytes Daten von ptr an die Ruby-Zeichenkette an.

rb_str_cat2(VALUE str, const char* ptr)
rb_str_cat_cstr(VALUE str, const char* ptr)

Hängt die C-Zeichenkette ptr an die Ruby-Zeichenkette str an. Diese Funktion ist äquivalent zu 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)

Hängt die C-Zeichenkette format und nachfolgende Argumente an die Ruby-Zeichenkette str gemäß einem printf-ähnlichen Format an. Diese Funktionen sind äquivalent zu rb_str_append(str, rb_sprintf(format, …)) bzw. 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)

Erstellt eine neue Ruby-Zeichenkette mit der angegebenen Kodierung.

rb_enc_str_new_literal(const char *ptr, rb_encoding *enc)

Erstellt eine neue Ruby-Zeichenkette aus einem C-Zeichenketten-Literal mit der angegebenen Kodierung.

rb_usascii_str_new(const char *ptr, long len)
rb_usascii_str_new_cstr(const char *ptr)

Erstellt eine neue Ruby-Zeichenkette mit der Kodierung US-ASCII.

rb_usascii_str_new_literal(const char *ptr)

Erstellt eine neue Ruby-Zeichenkette aus einem C-Zeichenketten-Literal mit der Kodierung US-ASCII.

rb_utf8_str_new(const char *ptr, long len)
rb_utf8_str_new_cstr(const char *ptr)

Erstellt eine neue Ruby-Zeichenkette mit der Kodierung UTF-8.

rb_utf8_str_new_literal(const char *ptr)

Erstellt eine neue Ruby-Zeichenkette aus einem C-Zeichenketten-Literal mit der Kodierung UTF-8.

rb_str_resize(VALUE str, long len)

Ändert die Größe einer Ruby-Zeichenkette auf len Bytes. Wenn str nicht modifizierbar ist, löst diese Funktion eine Ausnahme aus. Die Länge von str muss im Voraus gesetzt werden. Wenn len kleiner als die alte Länge ist, werden die Inhalte jenseits von len Bytes verworfen, andernfalls, wenn len größer als die alte Länge ist, werden die Inhalte jenseits der alten Bytes nicht beibehalten und sind Müll. Beachten Sie, dass RSTRING_PTR(str) sich durch Aufruf dieser Funktion ändern kann.

rb_str_set_len(VALUE str, long len)

Setzt die Länge einer Ruby-Zeichenkette. Wenn str nicht modifizierbar ist, löst diese Funktion eine Ausnahme aus. Diese Funktion behält den Inhalt bis zu len Bytes bei, unabhängig von RSTRING_LEN(str). len darf die Kapazität von str nicht überschreiten.

rb_str_modify(VALUE str)

Bereitet eine Ruby-Zeichenkette zur Modifikation vor. Wenn str nicht modifizierbar ist, löst diese Funktion eine Ausnahme aus, oder wenn der Puffer von str geteilt wird, weist diese Funktion einen neuen Puffer zu, um ihn ungeteilt zu machen. Sie MÜSSEN diese Funktion immer aufrufen, bevor Sie den Inhalt mit RSTRING_PTR und/oder rb_str_set_len ändern.

Array-Funktionen

rb_ary_new()

Erstellt ein Array ohne Elemente.

rb_ary_new2(long len)
rb_ary_new_capa(long len)

Erstellt ein Array ohne Elemente und weist einen internen Puffer für len Elemente zu.

rb_ary_new3(long n, …)
rb_ary_new_from_args(long n, …)

Erstellt ein n-Element-Array aus den Argumenten.

rb_ary_new4(long n, VALUE *elts)
rb_ary_new_from_values(long n, VALUE *elts)

Erstellt ein n-Element-Array aus einem C-Array.

rb_ary_to_ary(VALUE obj)

Konvertiert das Objekt in ein Array. Entspricht Object#to_ary.

Es gibt viele Funktionen zur Operation mit Arrays. Sie können Abstürze verursachen, wenn andere Typen übergeben werden.

rb_ary_aref(int argc, const VALUE *argv, VALUE ary)

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

Hängt len Elemente von Objekten aus ptr an das Array an.

Ruby mit C erweitern

Neue Funktionen zu Ruby hinzufügen

Sie können dem Ruby-Interpreter neue Funktionen (Klassen, Methoden usw.) hinzufügen. Ruby stellt APIs zur Verfügung, um die folgenden Dinge zu definieren

Klassen- und Moduldefinition

Um eine Klasse oder ein Modul zu definieren, verwenden Sie die folgenden Funktionen

VALUE rb_define_class(const char *name, VALUE super)
VALUE rb_define_module(const char *name)

Diese Funktionen geben die neu erstellte Klasse oder das neu erstellte Modul zurück. Sie möchten diese Referenz möglicherweise in einer Variablen speichern, um sie später zu verwenden.

Um verschachtelte Klassen oder Module zu definieren, verwenden Sie die folgenden Funktionen

VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
VALUE rb_define_module_under(VALUE outer, const char *name)

Methoden- und Singleton-Methoden-Definition

Um Methoden oder Singleton-Methoden zu definieren, verwenden Sie diese Funktionen

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)

Das 'argc' stellt die Anzahl der Argumente an die C-Funktion dar, die kleiner als 17 sein muss. Aber ich bezweifle, dass Sie so viele benötigen werden.

Wenn 'argc' negativ ist, gibt es die Aufrufsequenz an, nicht die Anzahl der Argumente.

Wenn argc -1 ist, wird die Funktion aufgerufen als

VALUE func(int argc, VALUE *argv, VALUE obj)

wobei argc die tatsächliche Anzahl der Argumente ist, argv das C-Array der Argumente ist und obj der Empfänger ist.

Wenn argc -2 ist, werden die Argumente in einem Ruby-Array übergeben. Die Funktion wird aufgerufen wie

VALUE func(VALUE obj, VALUE args)

wobei obj der Empfänger ist und args das Ruby-Array mit den tatsächlichen Argumenten ist.

Es gibt noch einige weitere Funktionen zur Definition von Methoden. Eine nimmt eine ID als Namen der zu definierenden Methode. Siehe auch ID oder Symbol unten.

void rb_define_method_id(VALUE klass, ID name,
                         VALUE (*func)(ANYARGS), int argc)

Es gibt zwei Funktionen zur Definition von privaten/geschützten Methoden

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)

Schließlich definiert rb_define_module_function eine Modulfunktion, die private UND Singleton-Methoden des Moduls sind. Zum Beispiel ist sqrt eine Modulfunktion, die im Math Modul definiert ist. Sie kann wie folgt aufgerufen werden

Math.sqrt(4)

oder

include Math
sqrt(4)

Um Modulfunktionen zu definieren, verwenden Sie

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

Außerdem können funktionsähnliche Methoden, die private Methoden im Kernel Modul sind, mit verwendet werden

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

Um einen Alias für die Methode zu definieren,

void rb_define_alias(VALUE module, const char* new, const char* old);

Um einen Reader/Writer für ein Attribut zu definieren,

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

Um die 'allocate'-Klassenmethode zu definieren und aufzuheben,

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

func muss die klass als Argument nehmen und eine neu zugewiesene Instanz zurückgeben. Diese Instanz sollte so leer wie möglich sein, ohne teure (einschließlich externer) Ressourcen.

Wenn Sie eine bestehende Methode eines Vorfahren Ihrer Klasse überschreiben, können Sie sich auf verlassen

VALUE rb_call_super(int argc, const VALUE *argv)

Um anzugeben, ob Schlüsselwortargumente beim Aufruf von super übergeben werden

VALUE rb_call_super_kw(int argc, const VALUE *argv, int kw_splat)

kw_splat kann die folgenden möglichen Werte haben (verwendet von allen Methoden, die ein kw_splat Argument akzeptieren)

RB_NO_KEYWORDS

Keine Schlüsselwörter übergeben

RB_PASS_KEYWORDS

Schlüsselwörter übergeben, das letzte Argument sollte ein Hash von Schlüsselwörtern sein

RB_PASS_CALLED_KEYWORDS

Schlüsselwörter übergeben, wenn die aktuelle Methode mit Schlüsselwörtern aufgerufen wurde, nützlich für die Argumentdelegation

Um den Empfänger des aktuellen Gültigkeitsbereichs zu erreichen (wenn kein anderer Weg verfügbar ist), können Sie verwenden

VALUE rb_current_receiver(void)

Konstantendefinition

Wir haben 2 Funktionen, um Konstanten zu definieren

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

Ersteres dient zur Definition einer Konstante unter einer angegebenen Klasse/Modul. Letzteres dient zur Definition einer globalen Konstante.

Ruby-Funktionen aus C verwenden

Es gibt mehrere Möglichkeiten, Ruby-Funktionen aus C-Code aufzurufen.

Ruby-Programme in einem String auswerten

Der einfachste Weg, Ruby-Funktionalität aus einem C-Programm zu verwenden, ist die Auswertung des Strings als Ruby-Programm. Diese Funktion erledigt die Arbeit

VALUE rb_eval_string(const char *str)

Die Auswertung erfolgt im aktuellen Kontext, daher können auf aktuelle lokale Variablen der innersten Methode (die von Ruby definiert ist) zugegriffen werden.

Beachten Sie, dass die Auswertung eine Ausnahme auslösen kann. Es gibt eine sicherere Funktion

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

Sie gibt nil zurück, wenn ein Fehler aufgetreten ist. Außerdem ist *state null, wenn str erfolgreich ausgewertet wurde, andernfalls ungleich null.

ID oder Symbol

Sie können Methoden direkt aufrufen, ohne den String zu parsen. Zuerst muss ich etwas über ID erklären. ID ist die Ganzzahl, die Rubys Identifikatoren wie Variablennamen repräsentiert. Der Ruby-Datentyp, der ID entspricht, ist Symbol. Er kann aus Ruby in der Form aufgerufen werden

:Identifier

oder

:"any kind of string"

Sie können den ID-Wert aus einem String im C-Code abrufen, indem Sie verwenden

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

Sie können die ID aus einem Ruby-Objekt (Symbol oder String) abrufen, das als Argument übergeben wird, indem Sie verwenden

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

Diese Funktionen versuchen, das Argument in einen String zu konvertieren, wenn es kein Symbol oder String war. Die zweite Funktion speichert das konvertierte Ergebnis in *name und gibt 0 zurück, wenn der String kein bekanntes Symbol ist. Nach diesem Funktionsaufruf ist *name immer ein Symbol oder ein String, andernfalls ist es ein String, wenn das Ergebnis 0 ist. Die dritte Funktion nimmt einen NUL-terminierten C-String, nicht einen Ruby VALUE.

Sie können ein Symbol aus einem Ruby-Objekt (Symbol oder String) abrufen, das als Argument übergeben wird, indem Sie verwenden

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

Diese Funktionen sind ähnlich wie die obigen Funktionen, außer dass sie ein Symbol anstelle einer ID zurückgeben.

Sie können eine C-ID in ein Ruby- Symbol konvertieren, indem Sie verwenden

VALUE ID2SYM(ID id)

und um ein Ruby- Symbol Objekt in eine ID zu konvertieren, verwenden Sie

ID SYM2ID(VALUE symbol)

Ruby-Methode aus C aufrufen

Um Methoden direkt aufzurufen, können Sie die folgende Funktion verwenden

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

Diese Funktion ruft eine Methode auf dem recv auf, wobei der Methodenname durch das Symbol mid angegeben wird.

Zugriff auf Variablen und Konstanten

Sie können Klassenvariablen und Instanzvariablen über Zugriffsfunktionen abrufen. Globale Variablen können auch zwischen beiden Umgebungen geteilt werden. Es gibt keine Möglichkeit, auf Rubys lokale Variablen zuzugreifen.

Die Funktionen zum Zugreifen/Modifizieren von Instanzvariablen sind unten aufgeführt

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

id muss das Symbol sein, das durch rb_intern() abgerufen werden kann.

Um auf die Konstanten der Klasse/des Moduls zuzugreifen

VALUE rb_const_get(VALUE obj, ID id)

Siehe auch Konstantendefinition oben.

Informationsaustausch zwischen Ruby und C

Ruby-Konstanten, auf die von C zugegriffen werden kann

Wie in Abschnitt 1.3 angegeben, können die folgenden Ruby-Konstanten von C referenziert werden.

Qtrue
Qfalse

Boolesche Werte. Qfalse ist auch in C false (d.h. 0).

Qnil

Ruby nil im C-Kontext.

Globale Variablen, die zwischen C und Ruby geteilt werden

Informationen können zwischen den beiden Umgebungen über gemeinsam genutzte globale Variablen ausgetauscht werden. Um sie zu definieren, können Sie die unten aufgeführten Funktionen verwenden

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

Diese Funktion definiert die Variable, die von beiden Umgebungen gemeinsam genutzt wird. Der Wert der globalen Variablen, auf die von 'var' verwiesen wird, kann über die Ruby-Globale Variable namens 'name' zugegriffen werden.

Sie können schreibgeschützte (von Ruby aus, natürlich) Variablen mit der folgenden Funktion definieren.

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

Sie können gehookte Variablen definieren. Die Accessor-Funktionen (Getter und Setter) werden beim Zugriff auf die gehookten Variablen aufgerufen.

void rb_define_hooked_variable(const char *name, VALUE *var,
                               VALUE (*getter)(), void (*setter)())

Wenn Sie nur einen Setter oder Getter angeben müssen, setzen Sie 0 für den nicht benötigten Hook. Wenn beide Hooks 0 sind, verhält sich rb_define_hooked_variable() genauso wie rb_define_variable().

Die Prototypen der Getter- und Setter-Funktionen sind wie folgt

VALUE (*getter)(ID id, VALUE *var);
void (*setter)(VALUE val, ID id, VALUE *var);

Sie können auch eine Ruby-Globale Variable ohne eine entsprechende C-Variable definieren. Der Wert der Variable wird nur über Hooks gesetzt/geholt.

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

Die Prototypen der Getter- und Setter-Funktionen sind wie folgt

VALUE (*getter)(ID id);
void (*setter)(VALUE val, ID id);

C-Daten in ein Ruby-Objekt einkapseln

Manchmal müssen Sie Ihre struct in der C-Welt als Ruby-Objekt verfügbar machen. In einer solchen Situation können Sie durch die TypedData_XXX Makro-Familie den Zeiger auf die struct und das Ruby-Objekt gegenseitig konvertieren.

C-Struktur zu Ruby-Objekt

Sie können sval, einen Zeiger auf Ihre struct, mit dem nächsten Makro in ein Ruby-Objekt konvertieren.

TypedData_Wrap_Struct(klass, data_type, sval)

TypedData_Wrap_Struct() gibt ein erstelltes Ruby-Objekt als VALUE zurück.

Das klass-Argument ist die Klasse für das Objekt. Die klass sollte von rb_cObject abgeleitet sein, und der Allokator muss durch Aufruf von rb_define_alloc_func oder rb_undef_alloc_func gesetzt werden.

data_type ist ein Zeiger auf eine const rb_data_type_t, die beschreibt, wie Ruby die struct verwalten soll.

rb_data_type_t ist wie folgt definiert. Werfen wir einen Blick auf jedes Mitglied der Struktur.

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 ist ein Bezeichner dieser Instanz der Struktur. Er wird hauptsächlich zum Sammeln und Ausgeben von Statistiken verwendet. Der Bezeichner muss also im Prozess eindeutig sein, muss aber kein gültiger C- oder Ruby-Bezeichner sein.

Diese dmark / dfree Funktionen werden während der GC-Ausführung aufgerufen. Keine Objektzuweisungen sind währenddessen erlaubt, also weisen Sie keine Ruby-Objekte innerhalb davon zu.

dmark ist eine Funktion, um Ruby-Objekte zu markieren, auf die von Ihrer struct verwiesen wird. Sie muss alle Referenzen von Ihrer struct mit rb_gc_mark oder seiner Familie markieren, wenn Ihre struct solche Referenzen speichert.

dfree ist eine Funktion zum Freigeben der Zeigerzuweisung. Wenn dies RUBY_DEFAULT_FREE ist, wird der Zeiger einfach freigegeben.

dsize berechnet den Speicherverbrauch in Bytes durch die Struktur. Sein Parameter ist ein Zeiger auf Ihre Struktur. Sie können 0 als dsize übergeben, wenn es schwierig ist, eine solche Funktion zu implementieren. Es wird jedoch immer noch empfohlen, 0 zu vermeiden.

dcompact wird aufgerufen, wenn die Speicherkomprimierung stattgefunden hat. Referenzierte Ruby-Objekte, die von rb_gc_mark_movable() markiert wurden, können hier pro rb_gc_location() aktualisiert werden.

Sie müssen reserviert mit 0 füllen.

parent kann auf eine andere C-Typdefinition zeigen, von der das Ruby-Objekt geerbt wird. Dann akzeptiert TypedData_Get_Struct() auch abgeleitete Objekte.

Sie können „data“ mit einem beliebigen Wert für Ihren Gebrauch füllen. Ruby tut nichts mit diesem Mitglied.

flags ist ein bitweises OR der folgenden Flag-Werte. Da sie ein tiefes Verständnis des Garbage Collectors in Ruby erfordern, können Sie flags einfach auf 0 setzen, wenn Sie sich nicht sicher sind.

RUBY_TYPED_FREE_IMMEDIATELY

Dieses Flag bewirkt, dass der Garbage Collector dfree() sofort während der GC-Ausführung aufruft, wenn Ihre struct freigegeben werden muss. Sie können dieses Flag angeben, wenn dfree niemals die interne Sperre von Ruby (GVL) entsperrt.

Wenn dieses Flag nicht gesetzt ist, verzögert Ruby die Ausführung von dfree() und ruft dfree() gleichzeitig mit Finalizern auf.

RUBY_TYPED_WB_PROTECTED

Es zeigt an, dass die Implementierung des Objekts Schreibbarrieren unterstützt. Wenn dieses Flag gesetzt ist, kann Ruby die Garbage Collection des Objekts besser durchführen.

Wenn es gesetzt ist, sind Sie jedoch dafür verantwortlich, Schreibbarrieren in allen Implementierungen von Methoden dieses Objekts angemessen anzubringen. Andernfalls kann Ruby beim Ausführen abstürzen.

Weitere Informationen zu Schreibbarrieren finden Sie unter Generational GC.

RUBY_TYPED_FROZEN_SHAREABLE

Dieses Flag zeigt an, dass das Objekt ein teilbares Objekt ist, wenn das Objekt eingefroren ist. Weitere Details finden Sie unter Ractor support.

Wenn dieses Flag nicht gesetzt ist, kann das Objekt mit der Methode Ractor.make_shareable() kein teilbares Objekt werden.

Beachten Sie, dass dieses Makro eine Ausnahme auslösen kann. Wenn sval, das gewrappt werden soll, eine Ressource hält, die freigegeben werden muss (z. B. zugewiesener Speicher, Handle einer externen Bibliothek usw.), müssen Sie rb_protect verwenden.

Sie können die Struktur zuweisend und wrappen in einem Schritt, auf eine vorzuziehenere Weise.

TypedData_Make_Struct(klass, type, data_type, sval)

Dieses Makro gibt ein zugewiesenes T_DATA-Objekt zurück, das den Zeiger auf die ebenfalls zugewiesene Struktur wrappt. Dieses Makro funktioniert wie

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

Sie sollten jedoch dieses Makro anstelle von „Zuweisung dann Wrap“ wie im obigen Code verwenden, wenn es einfach zugewiesen wird, da letzteres eine NoMemoryError auslösen kann und sval in diesem Fall Speicherlecks verursacht.

Die Argumente klass und data_type funktionieren wie ihre Gegenstücke in TypedData_Wrap_Struct(). Ein Zeiger auf die zugewiesene Struktur wird sval zugewiesen, das ein Zeiger des angegebenen Typs sein sollte.

Deklaratives Markieren/Kompaktieren von Strukturreferenzen

In Fällen, in denen Ihre struct Ruby-Objekte referenziert, die einfache Werte sind und nicht in bedingter Logik oder komplexen Datenstrukturen gekapselt sind, wird ein alternativer Ansatz zum Markieren und Aktualisieren von Referenzen bereitgestellt, indem Offset-Referenzen zu den VALUES in Ihrer struct deklariert werden.

Dies ermöglicht es dem Ruby GC, das Markieren dieser Referenzen und die GC-Kompaktierung zu unterstützen, ohne dass dmark- und dcompact-Callbacks definiert werden müssen.

Sie müssen eine statische Liste von VALUE-Zeigern zu den Offsets innerhalb Ihrer struct definieren, wo sich die Referenzen befinden, und das Mitglied „data“ auf diese Referenzliste verweisen lassen. Die Referenzliste muss mit RUBY_END_REFS enden.

Einige Makros wurden bereitgestellt, um die Randreferenzierung zu erleichtern

Das folgende Beispiel stammt aus Dir (definiert in 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
};

Das Deklarieren einfacher Referenzen auf diese Weise ermöglicht es dem GC, das zugrunde liegende Objekt sowohl zu markieren als auch zu verschieben und die Referenz darauf während der Kompaktierung automatisch zu aktualisieren.

Ruby-Objekt zu C-Struktur

Um den C-Zeiger aus dem T_DATA-Objekt abzurufen, verwenden Sie das Makro TypedData_Get_Struct().

TypedData_Get_Struct(obj, type, &data_type, sval)

Ein Zeiger auf die Struktur wird der Variablen sval zugewiesen.

Siehe das folgende Beispiel für Details.

Beispiel - Erstellen der dbm-Erweiterung

OK, hier ist das Beispiel zum Erstellen einer Erweiterungsbibliothek. Dies ist die Erweiterung für den Zugriff auf DBMs. Der vollständige Quellcode ist im Verzeichnis ext/ im Ruby-Quellcode enthalten.

Verzeichnis erstellen

% mkdir ext/dbm

Erstellen Sie ein Verzeichnis für die Erweiterungsbibliothek im Verzeichnis ext.

Designen Sie die Bibliothek

Sie müssen die Bibliotheksfunktionen entwerfen, bevor Sie sie erstellen.

Schreiben Sie den C-Code

Sie müssen C-Code für Ihre Erweiterungsbibliothek schreiben. Wenn Ihre Bibliothek nur eine Quelldatei hat, ist die Wahl von „LIBRARY.c“ als Dateiname vorzuziehen. Wenn Ihre Bibliothek mehrere Quelldateien hat, vermeiden Sie es, „LIBRARY.c“ als Dateiname zu wählen. Es kann auf einigen Plattformen mit einer Zwischen-Datei „LIBRARY.o“ kollidieren. Beachten Sie, dass einige Funktionen in der mkmf-Bibliothek, die unten beschrieben werden, eine Datei „conftest.c“ zum Überprüfen der Kompilierung generieren. Sie sollten „conftest.c“ nicht als Namen einer Quelldatei wählen.

Ruby führt die Initialisierungsfunktion namens „Init_LIBRARY“ in der Bibliothek aus. Zum Beispiel wird „Init_dbm()“ beim Laden der Bibliothek ausgeführt.

Hier ist ein Beispiel für eine Initialisierungsfunktion.

#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");
}

Die dbm-Erweiterung wrappt die dbm-Struktur in der C-Umgebung mithilfe von TypedData_Make_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);
}

Dieser Code wrappt die dbmdata-Struktur in ein Ruby-Objekt. Wir vermeiden, DBM* direkt zu wrappen, da wir Grösseninformationen cachen wollen. Da Object.allocate einen gewöhnlichen T_OBJECT-Typ (anstelle von T_DATA) zuweist, ist es wichtig, entweder rb_define_alloc_func() aufzurufen, um ihn zu überschreiben, oder rb_undef_alloc_func() aufzurufen, um ihn zu löschen.

Um die dbmdata-Struktur aus einem Ruby-Objekt abzurufen, definieren wir das folgende Makro

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

Diese Art von kompliziertem Makro führt das Abrufen und Schließen der Überprüfung für DBM durch.

Es gibt drei Arten, Methodenargumente zu empfangen. Erstens empfangen Methoden mit einer festen Anzahl von Argumenten Argumente wie folgt

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));
    /* ... */
}

Das erste Argument der C-Funktion ist das self, die restlichen sind die Argumente der Methode.

Zweitens empfangen Methoden mit einer beliebigen Anzahl von Argumenten Argumente wie folgt

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 */
    }
    /* ... */
}

Das erste Argument ist die Anzahl der Methodenargumente, das zweite Argument ist das C-Array der Methodenargumente und das dritte Argument ist der Empfänger der Methode.

Sie können die Funktion rb_scan_args() verwenden, um die Argumente zu überprüfen und abzurufen. Das dritte Argument ist ein String, der angibt, wie Methodenargumente erfasst und den folgenden VALUE-Referenzen zugewiesen werden sollen.

Sie können nur die Argumentnummer mit rb_check_arity() überprüfen, was praktisch ist, wenn Sie die Argumente als Liste behandeln möchten.

Das Folgende ist ein Beispiel für eine Methode, die Argumente über ein Ruby-Array empfängt

static VALUE
thread_initialize(VALUE thread, VALUE args)
{
    /* ... */
}

Das erste Argument ist der Empfänger, das zweite ist das Ruby-Array, das die Argumente für die Methode enthält.

Hinweis: GC muss über globale Variablen informiert werden, die Ruby-Objekte referenzieren, aber nicht in die Ruby-Welt exportiert werden. Sie müssen sie schützen durch

void rb_global_variable(VALUE *var)

oder die Objekte selbst durch

void rb_gc_register_mark_object(VALUE object)

extconf.rb vorbereiten

Wenn die Datei extconf.rb existiert, wird sie ausgeführt, um Makefile zu generieren.

extconf.rb ist die Datei zur Überprüfung von Kompilierungsbedingungen usw. Sie müssen

require 'mkmf'

am Anfang der Datei einfügen. Sie können die folgenden Funktionen verwenden, um verschiedene Bedingungen zu prüfen.

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

Siehe MakeMakefile für die vollständige Dokumentation dieser Funktionen.

Der Wert der folgenden Variablen beeinflusst die 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

Compiler-/Linker-Flags sind normalerweise nicht portabel. Sie sollten stattdessen append_cppflags, append_cpflags und append_ldflags verwenden, anstatt die obigen Variablen direkt anzuhängen.

Normalerweise wird die Liste der Objektdateien automatisch durch Suche nach Quelldateien generiert. Sie müssen sie jedoch explizit definieren, wenn während des Builds Quellcode generiert wird.

Wenn eine Kompilierungsbedingung nicht erfüllt ist, sollten Sie „create_makefile“ nicht aufrufen. Die Makefile wird nicht generiert, die Kompilierung wird nicht durchgeführt.

Abhängigkeiten vorbereiten (Optional)

Wenn die Datei namens depend existiert, wird Makefile diese Datei einschließen, um Abhängigkeiten zu prüfen. Sie können diese Datei erstellen, indem Sie aufrufen

% gcc -MM *.c > depend

Es ist harmlos. Bereiten Sie es vor.

Makefile generieren

Versuchen Sie, die Makefile zu generieren durch

ruby extconf.rb

Wenn die Bibliothek im Verzeichnis vendor_ruby anstelle von site_ruby installiert werden soll, verwenden Sie die Option –vendor wie folgt.

ruby extconf.rb --vendor

Sie benötigen diesen Schritt nicht, wenn Sie die Erweiterungsbibliothek unter dem Verzeichnis ext des Ruby-Quellcodes ablegen. In diesem Fall erledigt die Kompilierung des Interpreters diesen Schritt für Sie.

make ausführen

Geben Sie

make

ein, um Ihre Erweiterung zu kompilieren. Sie benötigen diesen Schritt ebenfalls nicht, wenn Sie die Erweiterungsbibliothek unter dem Verzeichnis ext des Ruby-Quellcodes abgelegt haben.

Debuggen

Möglicherweise müssen Sie die Erweiterung mit rb_debug debuggen. Erweiterungen können statisch verknüpft werden, indem der Verzeichnisname in der Datei ext/Setup hinzugefügt wird, sodass Sie die Erweiterung mit dem Debugger inspizieren können.

Fertig! Jetzt haben Sie die Erweiterungsbibliothek

Sie können mit Ihrer Bibliothek alles tun, was Sie möchten. Der Autor von Ruby erhebt keine Einschränkungen für Ihren Code, abhängig von der Ruby-API. Nutzen, modifizieren, verteilen oder verkaufen Sie Ihr Programm frei.

Anhang A. Übersicht über Ruby-Header- und Quelldateien

Ruby-Headerdateien

Alles unter $repo_root/include/ruby wird mit make install installiert. Es sollte pro #include <ruby.h> von C-Erweiterungen eingeschlossen werden. Alle Symbole sind öffentliche APIs, mit Ausnahme von Symbolen, die mit rbimpl_ oder RBIMPL_ präfixiert sind. Dies sind Implementierungsdetails und sollten nicht von C-Erweiterungen verwendet werden.

Nur $repo_root/include/ruby/*.h, deren entsprechende Makros in der Headerdatei $repo_root/include/ruby.h definiert sind, dürfen von C-Erweiterungen eingeschlossen werden.

Headerdateien unter $repo_root/internal/ oder direkt unter der Wurzel $repo_root/*.h werden nicht mit make installiert. Es handelt sich um interne Header mit nur internen APIs.

Ruby-Sprachkern

class.c

Klassen und Module

error.c

Ausnahmeklassen und Ausnahmechanismus

gc.c

Speicherverwaltung

load.c

Bibliotheksladung

object.c

Objekte

variable.c

Variablen und Konstanten

Ruby-Syntaxparser

parse.y

Grammatikdefinition

parse.c

automatisch generiert aus parse.y

defs/keywords

reservierte Schlüsselwörter

lex.c

automatisch generiert aus Schlüsselwörtern

Ruby-Evaluator (auch YARV genannt)

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

Regulärer Ausdrucks-Engine (Onigumo)

regcomp.c
regenc.c
regerror.c
regexec.c
regparse.c
regsyntax.c

Dienstprogramme

debug.c

Debug-Symbole für C-Debugger

dln.c

dynamisches Laden

st.c

Allzweck-Hash-Tabelle

strftime.c

Formatieren von Zeiten

util.c

verschiedene Dienstprogramme

Implementierung des Ruby-Interpreters

dmyext.c
dmydln.c
dmyencoding.c
id.c
inits.c
main.c
ruby.c
version.c

gem_prelude.rb
prelude.rb

Klassenbibliothek

array.c

Array

bignum.c

Bignum

compar.c

Comparable

complex.c

Complex

cont.c

Fiber, Continuation

dir.c

Dir

enum.c

Enumerable

enumerator.c

Enumerator

file.c

File

hash.c

Hash

io.c

IO

marshal.c

Marshal

math.c

Math

numeric.c

Numeric, Integer, Fixnum, Float

pack.c

Array#pack, String#unpack

proc.c

Binding, Proc

process.c

Process

random.c

zufällige Zahl

range.c

Range

rational.c

Rational

re.c

Regexp, MatchData

signal.c

Signal

sprintf.c

String#sprintf

string.c

String

struct.c

Struct

time.c

Time

defs/known_errors.def

Errno::* Ausnahmeklassen

-> known_errors.inc

automatisch generiert

Mehrsprachigkeit

encoding.c

Encoding

transcode.c

Encoding::Converter

enc/*.c

Kodierungsklassen

enc/trans/*

Codepunkt-Zuordnungstabellen

goruby Interpreter-Implementierung

goruby.c
golf_prelude.rb     : goruby specific libraries.
  -> golf_prelude.c : automatically generated

Anhang B. Ruby Extension API Referenz

Typen

VALUE

Der Typ für das Ruby-Objekt. Tatsächliche Strukturen sind in ruby.h definiert, wie z. B. struct RString usw. Um auf die Werte in Strukturen zuzugreifen, verwenden Sie Castings-Makros wie RSTRING(obj).

Variablen und Konstanten

Qnil

nil-Objekt

Qtrue

wahr-Objekt (standardmäßiger wahrer Wert)

Qfalse

falsch-Objekt

C-Pointer-Umhüllung

Data_Wrap_Struct(VALUE klass, void (*mark)(), void (*free)(), void *sval)

Umschließt einen C-Pointer in ein Ruby-Objekt. Wenn das Objekt Verweise auf andere Ruby-Objekte hat, sollten diese durch die Markierungsfunktion während des GC-Prozesses markiert werden. Andernfalls sollte Mark NULL sein. Wenn dieses Objekt nicht mehr referenziert wird, wird der Zeiger von der Free-Funktion verworfen.

Data_Make_Struct(klass, type, mark, free, sval)

Dieses Makro allokiert Speicher mittels malloc(), weist ihn der Variablen sval zu und gibt die DATA zurück, die den Zeiger auf den Speicherbereich kapselt.

Data_Get_Struct(data, type, sval)

Dieses Makro ruft den Zeigerwert aus DATA ab und weist ihn der Variablen sval zu.

Prüfen von VALUE-Typen

RB_TYPE_P(value, type)

Ist value ein interner Typ (T_NIL, T_FIXNUM usw.)?

TYPE(value)

Interner Typ (T_NIL, T_FIXNUM usw.)

FIXNUM_P(value)

Ist value ein Fixnum?

NIL_P(value)

Ist value nil?

RB_INTEGER_TYPE_P(value)

Ist value ein Integer?

RB_FLOAT_TYPE_P(value)

Ist value ein Float?

void Check_Type(VALUE value, int type)

Stellt sicher, dass value vom gegebenen internen type ist oder löst einen TypeError aus

VALUE-Typkonvertierung

FIX2INT(value), INT2FIX(i)

Fixnum <-> Integer

FIX2LONG(value), LONG2FIX(l)

Fixnum <-> long

NUM2INT(value), INT2NUM(i)

Numeric <-> Integer

NUM2UINT(value), UINT2NUM(ui)

Numeric <-> vorzeichenloser Integer

NUM2LONG(value), LONG2NUM(l)

Numeric <-> long

NUM2ULONG(value), ULONG2NUM(ul)

Numeric <-> vorzeichenloser long

NUM2LL(value), LL2NUM(ll)

Numeric <-> long long

NUM2ULL(value), ULL2NUM(ull)

Numeric <-> vorzeichenloser 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 <-> Arbiträr große Integer-Puffer

NUM2DBL(value)

Numeric -> double

rb_float_new(f)

double -> Float

RSTRING_LEN(str)

String -> Länge der String-Daten in Bytes

RSTRING_PTR(str)

String -> Zeiger auf String-Daten Hinweis: Der resultierende Zeiger ist möglicherweise nicht NULL-terminiert

StringValue(value)

Object mit #to_str -> String

StringValuePtr(value)

Object mit #to_str -> Zeiger auf String-Daten

StringValueCStr(value)

Object mit #to_str -> Zeiger auf String-Daten ohne NULL-Bytes Es ist garantiert, dass die resultierenden Daten NULL-terminiert sind

rb_str_new2(s)

char * -> String

Klassen und Module definieren

VALUE rb_define_class(const char *name, VALUE super)

Definiert eine neue Ruby-Klasse als Unterklasse von super.

VALUE rb_define_class_under(VALUE module, const char *name, VALUE super)

Erstellt eine neue Ruby-Klasse als Unterklasse von super im Namensraum des Moduls.

VALUE rb_define_module(const char *name)

Definiert ein neues Ruby-Modul.

VALUE rb_define_module_under(VALUE module, const char *name)

Definiert ein neues Ruby-Modul im Namensraum des Moduls.

void rb_include_module(VALUE klass, VALUE module)

Schließt das Modul in die Klasse ein. Wenn die Klasse es bereits einschließt, wird es ignoriert.

void rb_extend_object(VALUE object, VALUE module)

Erweitert das Objekt um die Attribute des Moduls.

Globale Variablen definieren

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

Definiert eine globale Variable, die zwischen C und Ruby geteilt wird. Wenn der Name ein Zeichen enthält, das nicht Teil des Symbols sein darf, ist er für Ruby-Programme nicht sichtbar.

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

Definiert eine schreibgeschützte globale Variable. Funktioniert genauso wie rb_define_variable(), außer dass die definierte Variable schreibgeschützt ist.

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

Definiert eine virtuelle Variable, deren Verhalten durch ein Paar von C-Funktionen definiert ist. Die Getter-Funktion wird aufgerufen, wenn die Variable referenziert wird. Die Setter-Funktion wird aufgerufen, wenn die Variable auf einen Wert gesetzt wird. Der Prototyp für Getter-/Setter-Funktionen ist

VALUE getter(ID id)
void setter(VALUE val, ID id)

Die Getter-Funktion muss den Wert für den Zugriff zurückgeben.

void rb_define_hooked_variable(const char *name, VALUE *var, VALUE (*getter)(), void (*setter)())

Definiert eine Hooked-Variable. Es ist eine virtuelle Variable mit einer C-Variablen. Der Getter wird aufgerufen als

VALUE getter(ID id, VALUE *var)

und gibt einen neuen Wert zurück. Der Setter wird aufgerufen als

void setter(VALUE val, ID id, VALUE *var)
void rb_global_variable(VALUE *var)

Teilt dem GC mit, dass die globale C-Variable, die Ruby-Werte enthält, markiert werden muss.

void rb_gc_register_mark_object(VALUE object)

Teilt dem GC mit, das object zu schützen, das möglicherweise nirgends referenziert wird.

Konstantendefinition

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

Definiert eine neue Konstante unter der Klasse/dem Modul.

void rb_define_global_const(const char *name, VALUE val)

Definiert eine globale Konstante. Dies ist dasselbe wie

rb_define_const(rb_cObject, name, val)

Methodendefinition

rb_define_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)

Definiert eine Methode für die Klasse. func ist der Funktionszeiger. argc ist die Anzahl der Argumente. Wenn argc -1 ist, erhält die Funktion 3 Argumente: argc, argv und self. Wenn argc -2 ist, erhält die Funktion 2 Argumente, self und args, wobei args ein Ruby-Array der Methodenargumente ist.

rb_define_private_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)

Definiert eine private Methode für die Klasse. Die Argumente sind dieselben wie bei rb_define_method().

rb_define_singleton_method(VALUE klass, const char *name, VALUE (*func)(ANYARGS), int argc)

Definiert eine Singleton-Methode. Die Argumente sind dieselben wie bei rb_define_method().

rb_check_arity(int argc, int min, int max)

Prüft die Anzahl der Argumente, argc liegt im Bereich von min..max. Wenn max UNLIMITED_ARGUMENTS ist, wird die Obergrenze nicht geprüft. Wenn argc außerhalb des Bereichs liegt, wird ein ArgumentError ausgelöst.

rb_scan_args(int argc, VALUE *argv, const char *fmt, …)

Ruft Argumente aus argc und argv in gegebene VALUE-Referenzen ab, gemäß der Formatzeichenkette. Das Format kann in ABNF wie folgt beschrieben werden

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

Zum Beispiel bedeutet „12“, dass die Methode mindestens ein Argument benötigt und höchstens drei (1+2) Argumente empfängt. Die Formatzeichenkette muss daher von drei Variablenreferenzen gefolgt werden, die den erfassten Argumenten zugewiesen werden. Für ausgelassene Argumente werden Variablen auf Qnil gesetzt. NULL kann anstelle einer Variablenreferenz gesetzt werden, was bedeutet, dass das entsprechende erfasste Argument verworfen werden soll.

Die Anzahl der angegebenen Argumente, abzüglich eines optionalen Hashes oder eines Iteratorblocks, wird zurückgegeben.

rb_scan_args_kw(int kw_splat, int argc, VALUE *argv, const char *fmt, …)

Das gleiche wie rb_scan_args, außer dass das Argument kw_splat angibt, ob Schlüsselwortargumente bereitgestellt werden (anstatt durch den Aufruf von Ruby an die C-Funktion bestimmt zu werden). kw_splat sollte einer der folgenden Werte sein

RB_SCAN_ARGS_PASS_CALLED_KEYWORDS

Gleiches Verhalten wie rb_scan_args.

RB_SCAN_ARGS_KEYWORDS

Das letzte Argument sollte ein Hash sein, der als Schlüsselwörter behandelt wird.

RB_SCAN_ARGS_LAST_HASH_KEYWORDS

Behandelt ein letztes Argument als Schlüsselwörter, wenn es sich um einen Hash handelt, und andernfalls nicht.

int rb_get_kwargs(VALUE keyword_hash, const ID *table, int required, int optional, VALUE *values)

Ruft Argument-VALUEs ab, die an Schlüsselwörter gebunden sind, dirigiert durch table in values und löscht abgerufene Einträge aus keyword_hash. Die ersten required IDs, auf die von table verwiesen wird, sind obligatorisch, und die nachfolgenden optional (- optional - 1, wenn optional negativ ist) IDs sind optional. Wenn ein obligatorischer Schlüssel nicht in keyword_hash enthalten ist, wird „missing keyword“ ArgumentError ausgelöst. Wenn ein optionaler Schlüssel nicht in keyword_hash vorhanden ist, wird das entsprechende Element in values auf Qundef gesetzt. Wenn optional negativ ist, werden die restlichen keyword_hash ignoriert, andernfalls wird „unknown keyword“ ArgumentError ausgelöst.

Seien Sie gewarnt, die Behandlung von Schlüsselwortargumenten in der C-API ist weniger effizient als die Behandlung in Ruby. Erwägen Sie, eine Ruby-Wrapper-Methode um eine C-Funktion ohne Schlüsselwörter zu verwenden. ref: bugs.ruby-lang.org/issues/11339

VALUE rb_extract_keywords(VALUE *original_hash)

Extrahiert Paare, deren Schlüssel ein Symbol ist, in einen neuen Hash aus einem Hash-Objekt, auf das von original_hash verwiesen wird. Wenn der ursprüngliche Hash Nicht-Symbol-Schlüssel enthält, werden diese in einen anderen Hash kopiert und der neue Hash wird über original_hash gespeichert, andernfalls wird 0 gespeichert.

Ruby-Methoden aufrufen

VALUE rb_funcall(VALUE recv, ID mid, int narg, …)

Ruft eine Methode auf. Um mid aus einem Methodennamen abzurufen, verwenden Sie rb_intern(). Kann auch private/geschützte Methoden aufrufen.

VALUE rb_funcall2(VALUE recv, ID mid, int argc, VALUE *argv)
VALUE rb_funcallv(VALUE recv, ID mid, int argc, VALUE *argv)

Ruft eine Methode auf und übergibt Argumente als Array von Werten. Kann auch private/geschützte Methoden aufrufen.

VALUE rb_funcallv_kw(VALUE recv, ID mid, int argc, VALUE *argv, int kw_splat)

Wie rb_funcallv, wobei kw_splat angibt, ob Schlüsselwortargumente übergeben werden.

VALUE rb_funcallv_public(VALUE recv, ID mid, int argc, VALUE *argv)

Ruft eine Methode auf und übergibt Argumente als Array von Werten. Kann nur öffentliche Methoden aufrufen.

VALUE rb_funcallv_public_kw(VALUE recv, ID mid, int argc, VALUE *argv, int kw_splat)

Wie rb_funcallv_public, wobei kw_splat angibt, ob Schlüsselwortargumente übergeben werden.

VALUE rb_funcall_passing_block(VALUE recv, ID mid, int argc, const VALUE* argv)

Wie rb_funcallv_public, außer dass der aktuell aktive Block als Block beim Aufruf der Methode übergeben wird.

VALUE rb_funcall_passing_block_kw(VALUE recv, ID mid, int argc, const VALUE* argv, int kw_splat)

Wie rb_funcall_passing_block, wobei kw_splat angibt, ob Schlüsselwortargumente übergeben werden.

VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE passed_procval)

Wie rb_funcallv_public, außer dass passed_procval den Block angibt, der an die Methode übergeben werden soll.

VALUE rb_funcall_with_block_kw(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE passed_procval, int kw_splat)

Wie rb_funcall_with_block, wobei kw_splat angibt, ob Schlüsselwortargumente übergeben werden.

VALUE rb_eval_string(const char *str)

Kompiliert und führt den String als Ruby-Programm aus.

ID rb_intern(const char *name)

Gibt die ID zurück, die dem Namen entspricht.

char *rb_id2name(ID id)

Gibt den Namen zurück, der der ID entspricht.

char *rb_class2name(VALUE klass)

Gibt den Namen der Klasse zurück.

int rb_respond_to(VALUE obj, ID id)

Gibt wahr zurück, wenn das Objekt auf die durch id angegebene Nachricht reagiert.

Instanzvariablen

VALUE rb_iv_get(VALUE obj, const char *name)

Ruft den Wert der Instanzvariable ab. Wenn der Name nicht mit „@“ präfixiert ist, ist diese Variable von Ruby aus nicht zugänglich.

VALUE rb_iv_set(VALUE obj, const char *name, VALUE val)

Setzt den Wert der Instanzvariable.

Kontrollstruktur

VALUE rb_block_call(VALUE recv, ID mid, int argc, VALUE * argv, VALUE (*func) (ANYARGS), VALUE data2)

Ruft eine Methode auf recv auf, wobei der Methodenname durch das Symbol mid und argc Argumente in argv angegeben wird, wobei func als Block bereitgestellt wird. Wenn func als Block aufgerufen wird, empfängt es den Wert von yield als erstes Argument und data2 als zweites Argument. Wenn mit mehreren Werten geyieldet wird (in C, rb_yield_values(), rb_yield_values2() und rb_yield_splat()), wird data2 als Array verpackt, während die geyieldeten Werte über argc/argv der dritten/vierten Argumente abgerufen werden können.

VALUE rb_block_call_kw(VALUE recv, ID mid, int argc, VALUE * argv, VALUE (*func) (ANYARGS), VALUE data2, int kw_splat)

Wie rb_funcall_with_block, wobei kw_splat angibt, ob Schlüsselwortargumente übergeben werden.

[OBSOLET] VALUE rb_iterate(VALUE (*func1)(), VALUE arg1, VALUE (*func2)(), VALUE arg2)

Ruft die Funktion func1 auf und stellt func2 als Block bereit. func1 wird mit dem Argument arg1 aufgerufen. func2 empfängt den Wert von yield als erstes Argument und arg2 als zweites Argument.

Wenn rb_iterate in 1.9 verwendet wird, muss func1 eine Ruby-Methode aufrufen. Diese Funktion ist seit 1.9 veraltet; verwenden Sie stattdessen rb_block_call.

VALUE rb_yield(VALUE val)

Yieldet val als einzelnes Argument an den Block.

VALUE rb_yield_values(int n, …)

Yieldet n Argumente an den Block und verwendet ein C-Argument pro Ruby-Argument.

VALUE rb_yield_values2(int n, VALUE *argv)

Yieldet n Argumente an den Block, wobei alle Ruby-Argumente im C-argv-Array stehen.

VALUE rb_yield_values_kw(int n, VALUE *argv, int kw_splat)

Wie rb_yield_values2, wobei kw_splat angibt, ob Schlüsselwortargumente übergeben werden.

VALUE rb_yield_splat(VALUE args)

Wie rb_yield_values2, außer dass Argumente durch das Ruby-Array args angegeben werden.

VALUE rb_yield_splat_kw(VALUE args, int kw_splat)

Wie rb_yield_splat, wobei kw_splat angibt, ob Schlüsselwortargumente übergeben werden.

VALUE rb_rescue(VALUE (*func1)(ANYARGS), VALUE arg1, VALUE (*func2)(ANYARGS), VALUE arg2)

Ruft die Funktion func1 mit arg1 als Argument auf. Wenn während func1 eine Ausnahme auftritt, ruft es func2 mit arg2 als erstes Argument und dem Ausnahmeobjekt als zweites Argument auf. Der Rückgabewert von rb_rescue() ist der Rückgabewert von func1, wenn keine Ausnahme auftritt, andernfalls der von func2.

VALUE rb_ensure(VALUE (*func1)(ANYARGS), VALUE arg1, VALUE (*func2)(ANYARGS), VALUE arg2)

Ruft die Funktion func1 mit arg1 als Argument auf und dann func2 mit arg2, wenn die Ausführung beendet ist. Der Rückgabewert von rb_ensure() ist der von func1, wenn keine Ausnahme aufgetreten ist.

VALUE rb_protect(VALUE (*func) (VALUE), VALUE arg, int *state)

Ruft die Funktion func mit arg als Argument auf. Wenn während func keine Ausnahme aufgetreten ist, gibt sie das Ergebnis von func zurück und *state ist null. Andernfalls gibt sie Qnil zurück und setzt *state auf ungleich null. Wenn state NULL ist, wird es in beiden Fällen nicht gesetzt. Sie müssen die Fehlerinformationen mit rb_set_errinfo(Qnil) löschen, wenn Sie die abgefangene Ausnahme ignorieren.

void rb_jump_tag(int state)

Setzt die von rb_protect() und rb_eval_string_protect() abgefangene Ausnahme fort. state muss der zurückgegebene Wert von diesen Funktionen sein. Diese Funktion kehrt niemals zum Aufrufer zurück.

void rb_iter_break()

Verlässt den aktuellen innersten Block. Diese Funktion kehrt niemals zum Aufrufer zurück.

void rb_iter_break_value(VALUE value)

Verlässt den aktuellen innersten Block mit dem Wert. Der Block gibt den angegebenen Argumentwert zurück. Diese Funktion kehrt niemals zum Aufrufer zurück.

Ausnahmen und Fehler

void rb_warn(const char *fmt, …)

Gibt eine Warnmeldung gemäß einem printf-ähnlichen Format aus.

void rb_warning(const char *fmt, …)

Gibt eine Warnmeldung gemäß einem printf-ähnlichen Format aus, wenn $VERBOSE wahr ist.

void rb_raise(rb_eRuntimeError, const char *fmt, …)

Löst eine RuntimeError aus. fmt ist eine Formatzeichenkette wie printf().

void rb_raise(VALUE exception, const char *fmt, …)

Löst eine Klassenausnahme aus. fmt ist eine Formatzeichenkette wie printf().

void rb_fatal(const char *fmt, …)

Löst einen fatalen Fehler aus, beendet den Interpreter. Für fatale Fehler wird keine Ausnahmebehandlung durchgeführt, aber es wird sichergestellt, dass ensure-Blöcke ausgeführt werden.

void rb_bug(const char *fmt, …)

Beendet den Interpreter sofort. Diese Funktion sollte in Situationen aufgerufen werden, die durch einen Bug im Interpreter verursacht werden. Es werden keine Ausnahmebehandlung oder ensure-Ausführung durchgeführt.

Hinweis: In der Formatzeichenkette kann „%“PRIsVALUE für die Ausgabe von Object#to_s (oder Object#inspect, wenn das '+'-Flag gesetzt ist) verwendet werden (und das zugehörige Argument muss ein VALUE sein). Da dies mit „%i“ für Ganzzahlen in Formatzeichenketten kollidiert, verwenden Sie „%d“.

Threading

Ab Ruby 1.9 unterstützt Ruby native 1:1-Threading mit einem Kernel-Thread pro Ruby Thread-Objekt. Derzeit gibt es eine GVL (Global VM Lock), die die gleichzeitige Ausführung von Ruby-Code verhindert und von den Funktionen rb_thread_call_without_gvl und rb_thread_call_without_gvl2 freigegeben werden kann. Diese Funktionen sind schwer zu verwenden und in thread.c dokumentiert; verwenden Sie sie nicht, bevor Sie die Kommentare in thread.c gelesen haben.

void rb_thread_schedule(void)

Gibt dem Scheduler einen Hinweis, die Ausführung an einen anderen Thread zu übergeben.

Input/Output (IO) auf einem einzelnen Dateideskriptor

int rb_io_wait_readable(int fd)

Wartet unbegrenzt darauf, dass der angegebene FD lesbar wird, und erlaubt die Planung anderer Threads. Gibt einen wahren Wert zurück, wenn gelesen werden kann, und falsch bei einem nicht behebbaren Fehler.

int rb_io_wait_writable(int fd)

Wie rb_io_wait_readable, aber für Schreibbarkeit.

int rb_wait_for_single_fd(int fd, int events, struct timeval *timeout)

Ermöglicht das Warten auf einen einzelnen FD für ein oder mehrere Ereignisse mit einem angegebenen Timeout.

events ist eine Maske aus beliebigen Kombinationen der folgenden Werte

  • RB_WAITFD_IN - Warten auf Lesbarkeit von normalen Daten

  • RB_WAITFD_OUT - Warten auf Schreibbarkeit

  • RB_WAITFD_PRI - Warten auf Lesbarkeit dringender Daten

Verwenden Sie ein NULL-timeout, um unbegrenzt zu warten.

I/O-Multiplexing

Ruby unterstützt I/O-Multiplexing basierend auf dem select(2)-Systemaufruf. Die Linux-Manpage select_tut(2) <man7.org/linux/man-pages/man2/select_tut.2.html> bietet einen guten Überblick über die Verwendung von select(2), und die Ruby-API verfügt über analoge Funktionen und Datenstrukturen zur bekannten select-API. Ein Verständnis von select(2) ist erforderlich, um diesen Abschnitt zu verstehen.

typedef struct rb_fdset_t

Die Datenstruktur, die die von select(2) verwendete fd_set-Bitmap umschließt. Dies ermöglicht Ruby, FD-Sätze zu verwenden, die größer sind als die, die durch historische Einschränkungen auf modernen Plattformen zulässig sind.

void rb_fd_init(rb_fdset_t *)

Initialisiert die rb_fdset_t, sie muss vor anderen rb_fd_* Operationen initialisiert werden. Analog zum Aufruf von malloc(3) zum Zuweisen eines fd_set.

void rb_fd_term(rb_fdset_t *)

Zerstört die rb_fdset_t, gibt alle von ihr verwendeten Speicher und Ressourcen frei. Sie muss vor der zukünftigen Verwendung mit rb_fd_init reinitialisiert werden. Analog zum Aufruf von free(3) zum Freigeben von Speicher für ein fd_set.

void rb_fd_zero(rb_fdset_t *)

Löscht alle FDs aus der rb_fdset_t, analog zu FD_ZERO(3).

void rb_fd_set(int fd, rb_fdset_t *)

Fügt einen gegebenen FD in die rb_fdset_t ein, analog zu FD_SET(3).

void rb_fd_clr(int fd, rb_fdset_t *)

Entfernt einen gegebenen FD aus der rb_fdset_t, analog zu FD_CLR(3).

int rb_fd_isset(int fd, const rb_fdset_t *)

Gibt wahr zurück, wenn ein gegebener FD in der rb_fdset_t gesetzt ist, andernfalls falsch. Analog zu 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)

Analog zum select(2)-Systemaufruf, aber erlaubt die Planung anderer Ruby-Threads während des Wartens.

Wenn Sie nur auf einen einzelnen FD warten, bevorzugen Sie die Funktionen rb_io_wait_readable, rb_io_wait_writable oder rb_wait_for_single_fd, da diese für spezifische Plattformen (derzeit nur Linux) optimiert werden können.

Interpreter initialisieren und starten

Die Embedding-API-Funktionen sind unten aufgeführt (nicht für Erweiterungsbibliotheken erforderlich)

void ruby_init()

Initialisiert den Interpreter.

void *ruby_options(int argc, char **argv)

Verarbeitet Kommandozeilenargumente für den Interpreter. Und kompiliert die Ruby-Quelle zur Ausführung. Es gibt einen opaken Zeiger auf die kompilierte Quelle oder einen internen Sonderwert zurück.

int ruby_run_node(void *n)

Führt die gegebene kompilierte Quelle aus und beendet den Prozess. Gibt EXIT_SUCCESS zurück, wenn die Quelle erfolgreich ausgeführt wurde. Andernfalls gibt es einen anderen Wert zurück.

void ruby_script(char *name)

Gibt den Namen des Skripts ($0) an.

Hooks für Interpreter-Ereignisse

void rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)

Fügt eine Hook-Funktion für die angegebenen Interpreter-Ereignisse hinzu. events sollte ein OR-wert von sein

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

Die Definition von rb_event_hook_func_t ist unten

typedef void (*rb_event_hook_func_t)(rb_event_t event, VALUE data,
                                     VALUE self, ID id, VALUE klass)

Das dritte Argument ‚data‘ für rb_add_event_hook() wird als zweites Argument an die Hook-Funktion übergeben, was in 1.8 ein Zeiger auf den aktuellen NODE war. Siehe RB_EVENT_HOOKS_HAVE_CALLBACK_DATA unten.

int rb_remove_event_hook(rb_event_hook_func_t func)

Entfernt die angegebene Hook-Funktion.

Speichernutzung

void rb_gc_adjust_memory_usage(ssize_t diff)

Passt die Menge des registrierten externen Speichers an. Sie können dem GC mitteilen, wie viel Speicher von einer externen Bibliothek mit dieser Funktion verwendet wird. Das Aufrufen dieser Funktion mit positivem diff bedeutet, dass die Speichernutzung erhöht wird; ein neuer Speicherblock wird allokiert oder ein Block wird größer reallokiert. Das Aufrufen dieser Funktion mit negativem diff bedeutet, dass die Speichernutzung verringert wird; ein Speicherblock wird freigegeben oder ein Block wird kleiner reallokiert. Diese Funktion kann den GC auslösen.

Makros für Kompatibilität

Einige Makros zur Prüfung der API-Kompatibilitäten sind standardmäßig verfügbar.

NORETURN_STYLE_NEW

Bedeutet, dass das NORETURN-Makro im funktionalen Stil anstelle eines Präfixes ist.

HAVE_RB_DEFINE_ALLOC_FUNC

Bedeutet, dass die Funktion rb_define_alloc_func() bereitgestellt wird, was bedeutet, dass das Allokations-Framework verwendet wird. Dies ist dasselbe wie das Ergebnis von have_func(“rb_define_alloc_func”, “ruby.h”).

HAVE_RB_REG_NEW_STR

Bedeutet, dass die Funktion rb_reg_new_str() bereitgestellt wird, die ein Regexp-Objekt aus einem String-Objekt erstellt. Dies ist dasselbe wie das Ergebnis von have_func(“rb_reg_new_str”, “ruby.h”).

HAVE_RB_IO_T

Bedeutet, dass der Typ rb_io_t bereitgestellt wird.

USE_SYMBOL_AS_METHOD_NAME

Bedeutet, dass Symbole als Methodennamen zurückgegeben werden, z.B. Module#methods, #singleton_methods und so weiter.

HAVE_RUBY_*_H

Definiert in ruby.h und bedeutet, dass die entsprechende Header-Datei verfügbar ist. Wenn beispielsweise HAVE_RUBY_ST_H definiert ist, sollten Sie ruby/st.h und nicht nur st.h verwenden.

Header-Dateien, die diesen Makros entsprechen, können direkt aus Erweiterungsbibliotheken eingebunden werden.

RB_EVENT_HOOKS_HAVE_CALLBACK_DATA

Bedeutet, dass rb_add_event_hook() das dritte Argument 'data' akzeptiert, das an die gegebene Event-Hook-Funktion übergeben wird.

Definition von rückwärtskompatiblen Makros für Funktionen mit Schlüsselwortargumenten

Die meisten Ruby C-Erweiterungen sind darauf ausgelegt, mehrere Ruby-Versionen zu unterstützen. Um Ruby 2.7+ in Bezug auf die Trennung von Schlüsselwortargumenten korrekt zu unterstützen, müssen C-Erweiterungen `*_kw`-Funktionen verwenden. Diese Funktionen existieren jedoch nicht in Ruby 2.6 und älter. In diesen Fällen sollten Makros definiert werden, um die Verwendung desselben Codes in mehreren Ruby-Versionen zu ermöglichen. Hier sind Beispielmakros, die Sie in Erweiterungen verwenden können, die Ruby 2.6 (oder älter) unterstützen, wenn Sie die in Ruby 2.7 eingeführten `*_kw`-Funktionen verwenden.

#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_kw(c, a, kw) rb_eval_cmd(c, a, 0)
#endif

Anhang C. Funktionen, die für die Verwendung in extconf.rb verfügbar sind

Siehe Dokumentation für mkmf.

Anhang D. Generational GC

Ruby 2.1 führte einen generationalen Garbage Collector (genannt RGenGC) ein. RGenGC (größtenteils) behält die Kompatibilität bei.

Im Allgemeinen ist die Verwendung der als Write Barrier bezeichneten Technik in Erweiterungsbibliotheken für einen generationalen GC erforderlich (en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29). RGenGC funktioniert auch ohne Write Barriers in Erweiterungsbibliotheken.

Wenn Ihre Bibliothek die folgenden Tipps befolgt, kann die Leistung weiter verbessert werden. Insbesondere der Abschnitt „Zeiger nicht direkt berühren“ ist wichtig.

Inkompatibilität

Sie können das Feld RBASIC(obj)->klass nicht mehr direkt schreiben, da es jetzt ein konstanter Wert ist.

Grundsätzlich sollten Sie dieses Feld nicht beschreiben, da MRI erwartet, dass es ein unveränderliches Feld ist. Wenn Sie dies jedoch in Ihrer Erweiterung tun möchten, können Sie die folgenden Funktionen verwenden:

VALUE rb_obj_hide(VALUE obj)

Löscht das Feld RBasic::klass. Das Objekt wird zu einem internen Objekt. ObjectSpace::each_object kann dieses Objekt nicht finden.

VALUE rb_obj_reveal(VALUE obj, VALUE klass)

Setzt RBasic::klass auf klass zurück. Wir erwarten, dass 'klass' eine von rb_obj_hide() versteckte Klasse ist.

Write Barriers

RGenGC erfordert keine Write Barriers, um einen generationalen GC zu unterstützen. Die Berücksichtigung von Write Barriers kann jedoch die Leistung von RGenGC verbessern. Bitte beachten Sie die folgenden Tipps.

Zeiger nicht direkt berühren

In MRI (include/ruby/ruby.h) werden einige Makros zur Erfassung von Zeigern auf interne Datenstrukturen unterstützt, wie z.B. RARRAY_PTR(), RSTRUCT_PTR() und so weiter.

VERWENDEN SIE DIESE MAKROS NICHT, sondern verwenden Sie die entsprechenden C-APIs wie rb_ary_aref(), rb_ary_store() und so weiter.

Prüfen Sie, ob Write Barriers eingefügt werden sollen

Sie müssen sich keine Gedanken über Write Barriers machen, wenn Sie nur eingebaute Typen verwenden.

Wenn Sie T_DATA-Objekte unterstützen, sollten Sie die Verwendung von Write Barriers in Betracht ziehen.

Das Einfügen von Write Barriers in T_DATA-Objekte funktioniert nur mit den folgenden Objekttypen: (a) langlebige Objekte, (b) wenn eine riesige Anzahl von Objekten generiert wird und (c) Container-Objekte, die Verweise auf andere Objekte enthalten. Wenn Ihre Erweiterung solche T_DATA-Objekte bereitstellt, sollten Sie das Einfügen von Write Barriers in Betracht ziehen.

(a): kurzlebige Objekte werden nicht zu alten Generationsobjekten. (b): nur wenige alte Objekte haben keine Leistungsauswirkungen. (c): nur wenige Referenzen haben keine Leistungsauswirkungen.

Das Einfügen von Write Barriers ist ein sehr schwieriger Hack, es ist leicht, kritische Fehler einzuführen. Außerdem sind mit dem Einfügen von Write Barriers einige Overhead verbunden. Grundsätzlich empfehlen wir nicht, Write Barriers einzufügen. Bitte erwägen Sie sorgfältig die Risiken.

Kombinieren mit eingebauten Typen

Bitte erwägen Sie die Nutzung von eingebauten Typen. Die meisten eingebauten Typen unterstützen Write Barrier, sodass Sie diese verwenden können, um das manuelle Einfügen von Write Barriers zu vermeiden.

Wenn Ihr T_DATA beispielsweise Verweise auf andere Objekte hat, können Sie diese Verweise in ein Array verschieben. Ein T_DATA-Objekt hat dann nur einen Verweis auf ein Array-Objekt. Oder Sie können ein Struct-Objekt verwenden, um ein T_DATA-Objekt (ohne Referenzen) und ein Array, das Referenzen enthält, zusammenzufassen.

Mit der Verwendung solcher Techniken müssen Sie keine Write Barriers mehr einfügen.

Write Barriers einfügen

[NOCHMALS] Das Einfügen von Write Barriers ist ein sehr schwieriger Hack und es ist leicht, kritische Fehler einzuführen. Außerdem sind mit dem Einfügen von Write Barriers einige Overhead verbunden. Grundsätzlich empfehlen wir nicht, Write Barriers einzufügen. Bitte erwägen Sie sorgfältig die Risiken.

Bevor Sie Write Barriers einfügen, müssen Sie den RGenGC-Algorithmus kennen (gc.c hilft Ihnen dabei). Makros und Funktionen zum Einfügen von Write Barriers sind in include/ruby/ruby.h verfügbar. Ein Beispiel ist in iseq.c enthalten.

Für eine vollständige Anleitung zu RGenGC und Write Barriers siehe <bugs.ruby-lang.org/projects/ruby-master/wiki/RGenGC>.

Anhang E. RB_GC_GUARD zum Schutz vor vorzeitigem GC

C Ruby verwendet derzeit einen konservativen Garbage Collector, daher müssen VALUE-Variablen auf dem Stack oder in Registern sichtbar bleiben, um sicherzustellen, dass zugehörige Daten verwendbar bleiben. Optimierende C-Compiler sind nicht auf konservative Garbage Collection ausgelegt, daher können sie den ursprünglichen VALUE optimieren, selbst wenn der Code von Daten abhängt, die mit diesem VALUE verbunden sind.

Das folgende Beispiel veranschaulicht die Verwendung von RB_GC_GUARD, um sicherzustellen, dass der Inhalt von sptr gültig bleibt, während der zweite Aufruf von rb_str_new_cstr ausgeführt wird.

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 */

Im obigen Beispiel muss RB_GC_GUARD *nach* der letzten Verwendung von sptr platziert werden. Das Platzieren von RB_GC_GUARD vor der Dereferenzierung von sptr wäre nutzlos. RB_GC_GUARD ist nur für den Datentyp VALUE wirksam, nicht für konvertierte C-Datentypen.

RB_GC_GUARD wäre im obigen Beispiel überhaupt nicht notwendig, wenn nach der Dereferenzierung von sptr nicht-inline-Funktionsaufrufe auf 's' getätigt werden. In diesem Fall würde der Aufruf einer beliebigen nicht-inline-Funktion auf 's', wie z.B.:

rb_str_modify(s);

Stellt sicher, dass 's' auf dem Stack oder im Register bleibt, um zu verhindern, dass eine GC-Ausführung es vorzeitig freigibt.

Die Verwendung des RB_GC_GUARD-Makros ist dem Schlüsselwort „volatile“ in C vorzuziehen. RB_GC_GUARD hat die folgenden Vorteile:

  1. Die Absicht der Makronutzung ist klar.

  2. RB_GC_GUARD beeinflusst nur seinen Aufruf-Site, während „volatile“ jedes Mal, wenn die Variable verwendet wird, zusätzlichen Code generiert, was die Optimierung beeinträchtigt.

  3. „volatile“-Implementierungen können bei einigen Compilern und Architekturen fehlerhaft/inkonsistent sein. RB_GC_GUARD ist für fehlerhafte Systeme/Compiler anpassbar, ohne andere Systeme negativ zu beeinflussen.

Anhang F. Ractor-Unterstützung

Ractor(s) sind der Parallelisierungsmechanismus, der in Ruby 3.0 eingeführt wurde. Alle Ractor können parallel auf einem anderen OS-Thread laufen (unter Verwendung eines zugrunde liegenden systemseitig bereitgestellten Threads), daher sollte die C-Erweiterung Thread-sicher sein. Eine C-Erweiterung, die in mehreren Ractoren laufen kann, wird als „Ractor-sicher“ bezeichnet.

Ractor-Sicherheit rund um C-Erweiterungen hat die folgenden Eigenschaften:

  1. Standardmäßig werden alle C-Erweiterungen als Ractor-unsicher eingestuft.

  2. Ractor-unsichere C-Methoden können nur vom Haupt-Ractor aufgerufen werden. Wenn sie von einem Nicht-Haupt-Ractor aufgerufen werden, wird ein Ractor::UnsafeError ausgelöst.

  3. Wenn eine Erweiterung als Ractor-sicher markiert werden soll, sollte die Erweiterung rb_ext_extractor_safe(true) in der Init_-Funktion der Erweiterung aufrufen, und alle definierten Methoden werden als Ractor-sicher markiert.

Um eine „Ractor-sichere“ C-Erweiterung zu erstellen, müssen wir die folgenden Punkte prüfen:

  1. Teilen Sie keine nicht teilbaren Objekte zwischen Raktoren

    Beispielsweise kann eine globale Variable in C dazu führen, dass nicht teilbare Objekte zwischen Raktoren geteilt werden.

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

    Das set()- und get()-Paar kann nicht teilbare Objekte mithilfe von g_var teilen, und das ist Ractor-unsicher.

    Nicht nur die direkte Verwendung von globalen Variablen, auch indirekte Datenstrukturen wie globale st_tables können Objekte teilen, also seien Sie vorsichtig.

    Beachten Sie, dass Klassen- und Objektmodule teilbare Objekte sind, sodass Sie den Code „cFoo = rb_define_class(…)“ mit globalen C-Variablen beibehalten können.

  2. Prüfen Sie die Thread-Sicherheit der Erweiterung

    Eine Erweiterung sollte Thread-sicher sein. Der folgende Code ist beispielsweise nicht Thread-sicher:

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

    da die globale Variable g_called von Threads anderer Raktoren synchronisiert werden sollte. Um solche Datenrennen zu vermeiden, sollten einige Synchronisierungen verwendet werden. Prüfen Sie include/ruby/thread_native.h und include/ruby/atomic.h.

    Mit Ractors sind alle als Methodenparameter übergebenen Objekte und der Empfänger (self) garantiert aus dem aktuellen Ractor oder teilbar. Folglich ist es einfacher, Code Ractor-sicher zu machen, als Code allgemein Thread-sicher zu machen. Zum Beispiel müssen wir kein Array-Objekt sperren, um darauf zuzugreifen.

  3. Prüfen Sie die Thread-Sicherheit jeder verwendeten Bibliothek

    Wenn die Erweiterung auf eine externe Bibliothek angewiesen ist, wie z.B. eine Funktion foo() aus einer Bibliothek libfoo, sollte die Funktion libfoo foo() Thread-sicher sein.

  4. Machen Sie ein Objekt teilbar

    Dies ist nicht erforderlich, um eine Erweiterung Ractor-sicher zu machen.

    Wenn eine Erweiterung spezielle Objekte bereitstellt, die durch rb_data_type_t definiert sind, überlegen Sie, ob diese Objekte teilbar sein können oder nicht.

    Das Flag RUBY_TYPED_FROZEN_SHAREABLE zeigt an, dass diese Objekte teilbar sein können, wenn das Objekt eingefroren ist. Das bedeutet, dass die Mutation der verpackten Daten nicht erlaubt ist, wenn das Objekt eingefroren ist.

  5. Sonstiges

    Es gibt möglicherweise andere Punkte oder Anforderungen, die bei der Erstellung einer Ractor-sicheren Erweiterung berücksichtigt werden müssen. Dieses Dokument wird erweitert, sobald sie entdeckt werden.