Fragen zur Ruby C-Erweiterungs-API

Lesezeit: 9 Minuten

Benutzer-Avatar
Allsinn

Also hatte ich kürzlich das unglückliche Bedürfnis, eine C-Erweiterung für Ruby zu machen (aus Performance-Gründen). Da hatte ich Verständnisprobleme VALUE (und tue es immer noch), also habe ich in die Ruby-Quelle geschaut und Folgendes gefunden: typedef unsigned long VALUE; (Link zur Quelleaber Sie werden feststellen, dass es ein paar andere “Möglichkeiten” gibt, aber ich denke, es ist im Wesentlichen eine long; korrigiert mich, wenn ich falsch liege). Als ich das weiter untersuchte, fand ich etwas Interessantes Blogeintragwas sagt:

“…in einigen Fällen könnte das VALUE-Objekt die Daten SEIN, anstatt AUF die Daten ZU ZEIGEN.”

Was mich verwirrt, ist das, wenn ich versuche, einen String von Ruby an C zu übergeben und zu verwenden RSTRING_PTR(); auf der VALUE (von Ruby an die C-Funktion übergeben) und versuchen Sie, es damit zu ‘debuggen’ strlen(); es gibt 4 zurück. Stets 4.

Beispielcode:

VALUE test(VALUE inp) {
    unsigned char* c = RSTRING_PTR(inp);
    //return rb_str_new2(c); //this returns some random gibberish
    return INT2FIX(strlen(c));
}

Dieses Beispiel gibt immer 1 als Stringlänge zurück:

VALUE test(VALUE inp) {
    unsigned char* c = (unsigned char*) inp;
    //return rb_str_new2(c); // Always "\x03" in Ruby.
    return INT2FIX(strlen(c));
}

Manchmal sehe ich in Ruby eine Ausnahme, die besagt: “Modul kann nicht in String konvertiert werden” (oder etwas in dieser Richtung, Ich habe jedoch so viel mit dem Code herumgespielt, um das herauszufinden, dass ich den Fehler jetzt nicht reproduzieren kann Der Fehler würde auftreten, wenn ich es versuchte StringValuePtr(); [I’m a bit unclear what this exactly does. Documentation says it changes the passed paramater to char*] auf Eingang):

VALUE test(VALUE inp) {
    StringValuePtr(inp);
    return rb_str_new2((char*)inp); //Without the cast, I would get compiler warnings
} 

Der fragliche Ruby-Code lautet also: MyMod::test("blahblablah")

BEARBEITEN: Ein paar Tippfehler korrigiert und den Beitrag ein wenig aktualisiert.


Die Fragen

  1. Was genau macht VALUE imp halt? Ein Zeiger auf das Objekt/den Wert? Der Wert selbst?
  2. Wenn es den Wert selbst enthält: Wann tut es das, und gibt es eine Möglichkeit, dies zu überprüfen?
  3. Wie greife ich tatsächlich auf den Wert zu (da ich scheinbar auf fast alles zugreife aber
    der Wert)?

PS: Mein C-Verständnis ist nicht wirklich das Beste, aber es ist noch in Arbeit; Lesen Sie auch die Kommentare in den Codeschnipseln für eine zusätzliche Beschreibung (falls es hilft).

Vielen Dank!

Benutzer-Avatar
prägen

Rubinsaiten vs. C-Saiten

Beginnen wir zuerst mit Saiten. Bevor Sie versuchen, einen String in C abzurufen, ist es zunächst eine gute Angewohnheit, anzurufen StringValue(obj) auf Ihrem VALUE Erste. Dies stellt sicher, dass Sie sich am Ende wirklich mit einem Ruby-String befassen, denn wenn es nicht bereits ein String ist, wird es in einen umgewandelt, indem es ihn mit einem Aufruf an dieses Objekt erzwingt to_str Methode. Dies macht die Dinge sicherer und verhindert den gelegentlichen Segfault, den Sie sonst bekommen könnten.

Das nächste, worauf Sie achten sollten, ist, dass Ruby-Saiten dies nicht sind \0-terminiert, da Ihr C-Code erwarten würde, dass sie Dinge wie machen strlen usw. funktionieren wie erwartet. Rubys Saiten tragen stattdessen ihre Längenangabe mit sich – deshalb zusätzlich RSTRING_PTR(str) es gibt auch die RSTRING_LEN(str) Makro um die tatsächliche Länge zu ermitteln.

Na und StringValuePtr Jetzt gibt es das Nicht-Null-terminierte zurück char * für Sie – das ist großartig für Puffer, bei denen Sie eine separate Länge haben, aber nicht das, was Sie wollen, z strlen. Verwenden StringValueCStr Stattdessen wird die Zeichenfolge so geändert, dass sie auf Null endet, sodass sie sicher für die Verwendung mit Funktionen in C ist, die erwarten, dass sie auf Null endet. Versuchen Sie jedoch, dies nach Möglichkeit zu vermeiden, da diese Änderung viel weniger performant ist als das Abrufen der nicht nullterminierten Zeichenfolge, die überhaupt nicht geändert werden muss. Es überrascht, wenn man das im Auge behält, wie selten man tatsächlich “echte” C-Saiten braucht.

self als implizites VALUE-Argument

Ein weiterer Grund, warum Ihr aktueller Code nicht wie erwartet funktioniert, ist, dass jede C-Funktion, die von Ruby aufgerufen werden soll, übergeben wird self als implizit VALUE.

  • Keine Argumente in Ruby (zB obj.doit) werden übersetzt

    WERT doit (WERT selbst)

  • Feste Anzahl von Argumenten (>0, zB obj.doit(a, b)) übersetzt in

    WERT doit(WERT selbst, WERT a, WERT b)

  • Var args in Ruby (zB obj.doit(a, b=nil)) übersetzt in

    WERT doit(int argc, WERT *argv, WERT selbst)

in Rubin. Woran Sie also in Ihrem Beispiel gearbeitet haben, ist nicht die Zeichenfolge, die Ihnen von Ruby übergeben wird, aber tatsächlich der aktuelle Wert von self, das ist das Objekt, das der Empfänger war, als Sie diese Funktion aufgerufen haben. Eine korrekte Definition für Ihr Beispiel wäre

static VALUE test(VALUE self, VALUE input) 

ich habe es gemacht static um auf eine weitere Regel hinzuweisen, die Sie in Ihren C-Erweiterungen befolgen sollten. Machen Sie Ihre C-Funktionen nur dann öffentlich, wenn Sie beabsichtigen, sie von mehreren Quelldateien gemeinsam zu nutzen. Da dies bei Funktionen, die Sie an eine Ruby-Klasse anhängen, fast nie der Fall ist, sollten Sie sie als deklarieren static standardmäßig und machen sie nur dann öffentlich, wenn es einen triftigen Grund dafür gibt.

Was ist WERT und woher kommt er?

Nun zum schwierigeren Teil. Wenn Sie tief in die Interna von Ruby eintauchen, werden Sie die Funktion finden rb_objnew in gc.c. Hier können Sie sehen, dass jedes neu erstellte Ruby-Objekt zu einem wird VALUEindem er als einer von etwas namens gegossen wird freelist. Es ist definiert als:

#define freelist objspace->heap.freelist

Sie können sich das vorstellen objspace als eine riesige Karte, die jedes einzelne Objekt speichert, das zu einem bestimmten Zeitpunkt in Ihrem Code aktiv ist. Hier erfüllt auch der Garbage Collector seine Pflicht und die heap Insbesondere struct ist der Ort, an dem neue Objekte geboren werden. Die “Freelist” des Heaps wird wieder als an deklariert RVALUE *. Dies ist die C-interne Darstellung der in Ruby eingebauten Typen. Ein RVALUE ist eigentlich wie folgt definiert:

typedef struct RVALUE {
    union {
    struct {
        VALUE flags;        /* always 0 for freed obj */
        struct RVALUE *next;
    } free;
    struct RBasic  basic;
    struct RObject object;
    struct RClass  klass;
    struct RFloat  flonum;
    struct RString string;
    struct RArray  array;
    struct RRegexp regexp;
    struct RHash   hash;
    struct RData   data;
    struct RTypedData   typeddata;
    struct RStruct rstruct;
    struct RBignum bignum;
    struct RFile   file;
    struct RNode   node;
    struct RMatch  match;
    struct RRational rational;
    struct RComplex complex;
    } as;
    #ifdef GC_DEBUG
    const char *file;
    int   line;
    #endif
} RVALUE;

Das heißt, im Grunde eine Vereinigung von Kerndatentypen, die Ruby kennt. Etwas vermissen? Ja, Fixnums, Symbole, nil und boolesche Werte sind dort nicht enthalten. Das liegt daran, dass diese Art von Objekten direkt mit dargestellt werden unsigned long dass ein VALUE läuft am Ende darauf hinaus. Ich denke, die Designentscheidung dort war (abgesehen davon, dass es eine coole Idee ist), dass das Dereferenzieren eines Zeigers möglicherweise etwas weniger leistungsfähig ist als die Bitverschiebungen, die derzeit beim Transformieren von benötigt werden VALUE zu dem, was es eigentlich darstellt. Im Wesentlichen

obj = (VALUE)freelist;

sagt, gib mir was auch immer für Freelist-Punkte zur Zeit und behandle das so unsigned long. Dies ist sicher, da freelist ein Zeiger auf eine ist RVALUE – und ein Zeiger kann auch sicher interpretiert werden als unsigned long. Dies impliziert, dass jeder VALUE mit Ausnahme derjenigen, die Fixnums, Symbole, Null oder Boolesche Werte tragen, sind im Wesentlichen Zeiger auf ein RVALUEdie anderen sind direkt innerhalb der vertreten VALUE.

Ihre letzte Frage, wie können Sie überprüfen, was a VALUE steht für? Du kannst den … benutzen TYPE(x) Makro, um zu prüfen, ob a VALUE‘s Typ wäre einer der “primitiven”.

  • Ich bin nach einer Weile versehentlich auf diese Frage zurückgekommen, und verdammt, diese Antwort wurde sexier.

    – Alleskönner

    22. August 2012 um 15:29 Uhr

  • Was ist, wenn Sie Binärdaten in Ihrem Ruby “String” haben, die Null-Bytes enthalten können? Welches Makro oder welche Funktion sollte ich verwenden, um die zu konvertieren VALUE input in ein unsigned char * ?

    – Tilo

    18. Juni 2013 um 20:29 Uhr

  • @ Tilo Du kannst RSTRING_PTR(VALUE ruby_string) oder StringValuePtr(VALUE ruby_string)darauf. Es wird Ihnen die geben unsigned char* du schaust nach. Es wird jedoch nicht nullterminiert, sodass Sie die in Cs integrierten Zeichenfolgenfunktionen nicht verwenden können, aber wenn es Binärdaten enthält, müssen Sie sie wahrscheinlich sowieso nicht verwenden! 😀

    – Alleskönner

    25. Juni 2013 um 18:39 Uhr

  • Danke, dein Einblick für StringValueCStr Sein schwer war auf jeden Fall hilfreich, ich habe versucht, eine leistungsstarke Erweiterung zu erstellen, und benötigte solche Optimierungen

    – Francesco Belladonna

    29. Oktober 2015 um 16:47 Uhr

Benutzer-Avatar
Mon ouïe

VALUE test(VALUE inp)

Das erste Problem ist hier: inp ist self (also in Ihrem Fall das Modul). Wenn Sie sich auf das erste Argument beziehen möchten, müssen Sie davor ein Selbstargument hinzufügen (was mich dazu bringt, hinzuzufügen -Wno-unused-parameters zu meinen cflags, da es bei Modulfunktionen nie verwendet wird):

VALUE test(VALUE self, VALUE inp)

Ihr erstes Beispiel verwendet ein Modul als Zeichenfolge, was sicherlich nichts Gutes ergibt. RSTRING_PTR Typüberprüfungen fehlen, was ein guter Grund ist, es nicht zu verwenden.

Ein VALUE ist eine Referenz auf das Ruby-Objekt, aber nicht direkt ein Zeiger darauf, was es enthalten kann (wie ein char* im Fall eines Strings). Sie müssen diesen Zeiger je nach Objekt mithilfe einiger Makros oder Funktionen abrufen. Für eine Zeichenfolge, die Sie möchten StringValuePtr (oder StringValueCStr um sicherzustellen, dass die Zeichenfolge nullterminiert ist) which kehrt zurück der Zeiger (es tut es nicht Rückgeld den Inhalt Ihres VALUE in irgendeiner Weise).

strlen(StringValuePtr(thing));
RSTRING_LEN(thing); /* I assume strlen was just an example ;) */

Der eigentliche Inhalt der VALUE ist, zumindest in MRI und YARV, die object_id des Objekts (oder zumindest nach einer Bitverschiebung).

Für Ihre eigenen Objekte enthält der WERT höchstwahrscheinlich einen Zeiger auf ein C-Objekt, das Sie verwenden können Data_Get_Struct:

 my_type *thing = NULL;
 Data_Get_Struct(rb_thing, my_type, thing);

  • +1, nur eine Bemerkung: Es sollte VALUE test (VALUE self, VALUE inp) sein, nein? self immer am Anfang für Funktionen mit festen Argumenten?

    – Prägung

    13. August 2011 um 16:11 Uhr

  • Ah +1 für die beiden großartigen Fragen. Ich weiß nicht welchen ich annehmen soll. Ich neige leicht zur Antwort von @emboss, da sie detaillierter und erklärender ist, aber beide waren großartig und hilfreich … Einhörner unter den Antworten, würde ich sagen. 😀

    – Alleskönner

    13. August 2011 um 20:36 Uhr

1076790cookie-checkFragen zur Ruby C-Erweiterungs-API

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy