16

そのため、最近、Ruby 用の C 拡張機能を作成する必要がありました (パフォーマンスのため)。私は理解に問題を抱えていたのでVALUE(そして今でもそうです)、Rubyソースを調べて見つけました:(typedef unsigned long VALUE;Link to Source、しかし、それが行われる他のいくつかの「方法」があることに気付くでしょうが、それは本質的にlong; 間違っていたら訂正してください)。そのため、これをさらに調査しているときに、次のような興味深いブログ投稿を見つけました。

「...場合によっては、データを指すのではなく、値オブジェクトがデータである可能性があります。」

私を混乱させるのは、Ruby から C に文字列を渡しRSTRING_PTR();て (Ruby から C 関数に渡された) を使用し、それを使用しVALUEて「デバッグ」しようとすると、 4strlen();が返されることです。常に4.

コード例:

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

この例では、文字列の長さとして常に 1 が返されます。

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

ruby では、「モジュールを文字列に変換できません」という例外が表示されることがあります (または、これらの行に沿った何か、ただし、これを理解しようとしてコードをいじっていたので、今はエラーを再現できません私が試したときにエラーが発生しましたStringValuePtr();[これが正確に何をするのか少しわかりません。ドキュメントには、渡さchar*れたパラメーターが inp の ] に変更されると書かれています):

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

したがって、問題の Ruby コードは次のとおりです。MyMod::test("blahblablah")

編集: いくつかのタイプミスを修正し、投稿を少し更新しました。


質問

  1. 正確には何がVALUE imp保持されますか?オブジェクト/値へのポインタ? 価値そのもの?
  2. 値自体を保持している場合:いつそれを行い、それを確認する方法はありますか?
  3. 実際に値にアクセスするにはどうすればよいですか (値以外のほとんどすべてにアクセスしているように見えるため)

PS: C についての私の理解は、実際には最善ではありませんが、進行中の作業です。また、追加の説明については、コード スニペットのコメントをお読みください (役立つ場合)。

ありがとう!

4

2 に答える 2

30

Ruby 文字列 vs. C 文字列

まずは文字列から始めましょう。まず第一に、C で文字列を取得しようとする前に、最初に呼び出すことStringValue(obj)をお勧めしますVALUE。これにより、最終的に Ruby 文字列を実際に処理できるようになります。これは、まだ文字列でない場合は、そのオブジェクトのto_strメソッドへの呼び出しで強制的に文字列に変換するためです。したがって、これにより物事がより安全になり、他の方法で発生する可能性のあるセグメンテーション違反を防ぐことができます。

次に気をつけなければならないことは、Ruby の文字列が -terminate されていないことです。これは、C コードがetc. を期待どおりに動作\0させることを期待しているためです。strlenRuby の文字列は、代わりに長さの情報を持っています。そのため、実際の長さを決定するマクロRSTRING_PTR(str)もあります。RSTRING_LEN(str)

つまり、StringValuePtrゼロ以外で終了char *するものを返すようになりました。これは、別の長さを持つバッファーには最適ですが、たとえばstrlen. 代わりに使用StringValueCStrすると、文字列がゼロで終わるように変更されるため、ゼロで終わることが期待される C の関数で安全に使用できます。ただし、この変更は、まったく変更する必要のない非ゼロ終了文字列を取得するよりもパフォーマンスがはるかに低いため、可能な限りこれを避けるようにしてください。これに注目していると、実際に「本物の」C 文字列が必要になることはめったにないことに驚くでしょう。

暗黙の VALUE 引数としての self

現在のコードが期待どおりに機能しないもう 1 つの理由は、Ruby によって呼び出されるすべての C 関数がself暗黙的な として渡されることVALUEです。

  • Ruby の引数なし (例: obj.doit ) は、次のように変換されます。

    VALUE doit(VALUE セルフ)

  • 固定量の引数 (>0、例: obj.doit(a, b)) は次のように変換されます

    VALUE doit(VALUE 自分、VALUE a、VALUE b)

  • Ruby の var 引数 (例: obj.doit(a, b=nil)) は、次のように変換されます。

    VALUE doit(int argc, VALUE *argv, VALUE self)

ルビーで。したがって、例で作業していたのは、Ruby から渡された文字列ではなくself、実際には の現在の値です。これは、その関数を呼び出したときにレシーバーであったオブジェクトです。あなたの例の正しい定義は次のようになります

static VALUE test(VALUE self, VALUE input) 

staticC 拡張で従うべき別のルールを指摘するために作成しました。複数のソース ファイル間で C 関数を共有する場合にのみ、C 関数をパブリックにします。Ruby クラスにアタッチする関数については、このようなケースはほとんどないため、staticデフォルトで宣言し、正当な理由がある場合にのみ公開する必要があります。

価値とは何か、それはどこから来るのか?

今、難しい部分に。Ruby の内部を深く掘り下げると、gc.c に関数rb_objnewが見つかります。VALUEここで、新しく作成された Ruby オブジェクトは、と呼ばれるものから 1 つとしてキャストされることによって、 になることがわかりますfreelist。次のように定義されています。

#define freelist objspace->heap.freelist

objspaceは、コード内の特定の時点で現在有効なすべてのオブジェクトを格納する巨大なマップであると想像できます。これは、ガベージ コレクターが義務を果たす場所でもあり、heap特に構造体は新しいオブジェクトが生まれる場所です。ヒープの「フリーリスト」は、RVALUE *. これは、Ruby 組み込み型の C 内部表現です。AnRVALUEは実際には次のように定義されます。

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;

つまり、基本的に Ruby が認識しているコア データ型の結合です。何か足りない?はい、Fixnum、Symbol、nilブール値は含まれていません。これは、これらの種類のオブジェクトが、最終的に に要約される を使用して直接表現unsigned longVALUEれるためです。ポインタを逆参照することは、ポインタをVALUE実際に表すものに変換するときに現在必要とされているビット シフトよりもわずかにパフォーマンスが低い可能性があるという設計上の決定 (クールなアイデアであることに加えて) があったと思います。本質的に

obj = (VALUE)freelist;

は、現在 freelist が指しているすべてのものを私に与えて、 として扱うと言いますunsigned long。freelist はRVALUE- へのポインターであり、ポインターは としても安全に解釈できるため、これは安全unsigned longです。これはVALUE、Fixnum、シンボル、nil、またはブール値を持つものを除くすべてが、本質的に へのポインタでありRVALUE、その他は 内で直接表現されることを意味しますVALUE

最後の質問ですが、a が何をVALUE表しているかを確認するにはどうすればよいですか? TYPE(x)マクロを使用して、 aVALUEのタイプが「プリミティブ」タイプの 1 つであるかどうかを確認できます。

于 2011-08-13T15:56:20.230 に答える
5
VALUE test(VALUE inp)

最初の問題はここにあります: inp は自己です (つまり、あなたの場合はモジュールです)。最初の引数を参照したい場合は、その前に self 引数を追加する必要があります (-Wno-unused-parametersモジュール関数の場合は決して使用されないため、cflags に追加する必要があります)。

VALUE test(VALUE self, VALUE inp)

最初の例では、モジュールを文字列として使用していますが、これは確かに良い結果にはなりません。RSTRING_PTRには型チェックがありません。これは、使用しない正当な理由です。

VALUE は Ruby オブジェクトへの参照ですが、内容への直接のポインタではありません (文字列の場合の char* のように)。各オブジェクトに応じて、いくつかのマクロまたは関数を使用してそのポインターを取得する必要があります。文字列の場合は、ポインターを返すStringValuePtr必要があります(またはStringValueCStr文字列が null で終わるようにする必要があります) (値の内容はまったく変更されません)。

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

の実際のコンテンツは、VALUE少なくとも MRI および YARV ではobject_id、オブジェクトの です (または、少なくともビットシフトの後である)。

独自のオブジェクトの場合、VALUE には、次を使用して取得できる C オブジェクトへのポインターが含まれる可能性が高くなりますData_Get_Struct

 my_type *thing = NULL;
 Data_Get_Struct(rb_thing, my_type, thing);
于 2011-08-13T15:16:12.780 に答える