Ruby拡張ライブラリを書いてみる。その2
前回をふまえて、もう少し複雑な拡張ライブラリを書いてみます。
対象を「値 v と場所(Peer) p を記憶して、値 v が格納されている場所 p の配列を返すデータベース」としてみます。
typedef std::vector<int> IntArray; class Cache { Cache(); virtual Cache(); void insert(int v, int p); // 値v が場所p に格納されている事を記憶する。 IntArray find(int v); // 値v が格納されている場所p の配列を返す。ただし status==ENABLEの場所に限る。 void erase(int v); // 全ての場所にある値v を消去する。 void erase_peer(int p); // 場所p を消去する。 int get_peer_status(int p); // 場所p のステータスを取得する(0:DISABLE, 1:ENALBE)。 void set_peer_status(int p, int s); // 場所p のステータスを設定する。 };
Peerの扱いがアレなので、Peerクラスを導入してみます。が、その前にCacheとPeerを紐付ける為に、Peerの集合Peersを導入します。
リファクタレベルの話ですね。
class Peers { public: Peers(Cache& c) { _c = &c; }; virtual Peers() {}; inline Peer* get_peer(int p) { return new Peer(*_c, p); }; private: Cache* _c; };
Peer本体です。
class Peer { Peer(Cache& c, int p) { _c = &c, _p = p; }; virtual Peer() {}; inline int insert(int v) { _c->insert(_p, v); }; inline void erase() { _c->erase_peer(_p); }; inline int get_status() { return _c->get_peer_status(_p); }; inline void set_status(int s) { _c->set_peer_status(_p, s); }; private: Cache* _c; int _p; };
これをRuby拡張ライブラリにします。
- Rubyに登録できる関数は引数と戻り値はVALUE型なのでこれらのラッパー(rb_*)を用意します。
- アロケータ(rb_alloc)とそこで利用するデストラクタ(free)、VALUE selfからC++のインスタンスを取得する関数(get_inst)も用意します。
- rb_define_class, rb_define_class_under の戻り値を覚える場所を確保します。これは子クラスを生成する際に利用します。
static VALUE rb_cCache, rb_cPeer, rb_cPeers; class Cache { // 追加分 static VALUE rb_alloc(VALUE self); static void free(void* obj) { delete (Cache*)obj; }; static Cache* get_inst(VALUE self); static VALUE rb_find(VALUE self, VALUE v); static VALUE rb_erase(VALUE self, VALUE v); }; class Peers { // 追加分 static VALUE rb_alloc(VALUE self); static void free(void* obj) { delete (Peers*)obj; }; static Peers* get_inst(VALUE self); static VALUE rb_get_peer(VALUE self, VALUE p); }; class Peer { // 追加分 static VALUE rb_alloc(VALUE self, VALUE p); static void free(void* obj) { delete (Peer*)obj; }; static Peer* get_inst(VALUE self); static VALUE rb_insert(VALUE self, VALUE v); static VALUE rb_erase(VALUE self); static VALUE rb_get_status(VALUE self); static VALUE rb_set_status(VALUE self, VALUE s); };
各クラスのstaticメソッドを実装してみます。
- Cacheクラスは前回のケースと変わりません。findはblockを渡された場合はyield, そうでない場合はArrayを返すようにしてみました。
VALUE Cache::rb_alloc(VALUE self) { return Data_Wrap_Struct(self, 0, free, new Cache); } Cache* Cache::get_inst(VALUE self) { Cache* r; Data_Get_Struct(self, Cache, r); return r; } VALUE Cache::rb_find(VALUE self, VALUE v) { Cache* c = get_inst(self); IntArray = c->find(NUM2INT(v)); // yield if(rb_block_given_p()) { for(IntArray::iterator it = a.begin(); it != a.end(); it++) { rb_yield(INT2NUM(*it)); } return Qnil; } // Array ID push = rb_intern("push"); VALUE stub = Qnil; VALUE r = rb_class_new_instance(0, &stub, rb_cArray); for(IntArray::iterator it = a.begin(); it != a.end(); it++) { rb_funcall(r, push, 1, INT2NUM(*it)); } return r; } VALUE Cache::rb_erase(VALUE self, VALUE v) { Cache* c = get_inst(self); c->erase(NUM2INT(v)); return Qnil; }
- Peersクラスはallocが少し異なり、ここでstatic変数rb_cPeersを利用します。
VALUE Peers::rb_alloc(VALUE self) { Cache* c = Cache::get_inst(self); // self から Cache を受け取り… return Data_Wrap_Struct(rb_cPeers, 0, free, new Peers(*c)); // Peers::newへ渡します。第1引数がselfでないのがポイント。 } Peers* Peers::get_inst(VALUE self) { Peers* r; Data_Get_Struct(self, Peers, r); return r; }
- Peerクラス。ここでもstatic変数rb_cPeerを利用してallocします。
VALUE Peer::rb_alloc(VALUE self, VALUE p) { Peers* p = Peers::get_inst(self); return Data_Wrap_Struct(rb_cPeer, 0, free, new Peer(*p, NUM2INT(p))); } Peer* Peer::get_inst(VALUE self) { Peer* r; Data_Get_Struct(self, Peer, r); return r; } VALUE Peer::rb_insert(VALUE self, VALUE v) { Peer* p = get_inst(self); return INT2NUM(c->insert(NUM2INT(v))); } VALUE Peer::rb_erase(VALUE self) { Peer* p = get_inst(self); p->erase(); return Qnil; } VALUE Peer::rb_get_status(VALUE self) { Peer* p = get_inst(self); return INT2NUM(p->get_status()); } VALUE Peer::rb_set_status(VALUE self, VALUE s) { Peer* p = get_inst(self); p->set_status(NUM2INT(s)); return Qnil; }
- Rubyが認識するためのInitは以下の様になります。DISABLE, ENABLEも定数定義しました。
extern "C" void Init_cache(void) { rb_cCache = rb_define_class("Cache", rb_cObject); rb_define_alloc_func(rb_cCache, Cache::rb_alloc); rb_define_method(rb_cCache, "find", RUBY_METHOD_FUNC(Cache::rb_find), 1); rb_define_method(rb_cCache, "erase", RUBY_METHOD_FUNC(Cache::rb_erase), 1); rb_define_method(rb_cCache, "peers", RUBY_METHOD_FUNC(Peers::rb_alloc), 0); rb_cPeers = rb_define_class_under(rb_cCache, "Peers", rb_cCache); rb_define_method(rb_cPeers, "[]", RUBY_METHOD_FUNC(Peer::rb_alloc), 1); rb_cPeer = rb_define_class_under(rb_cCache, "Peer" , rb_cPeers); rb_define_method(rb_cPeer, "status", RUBY_METHOD_FUNC(Peer::rb_get_status), 0); rb_define_method(rb_cPeer, "status=", RUBY_METHOD_FUNC(Peer::rb_set_status), 1); rb_define_method(rb_cPeer, "insert", RUBY_METHOD_FUNC(Peer::rb_insert), 1); rb_define_method(rb_cPeer, "erase", RUBY_METHOD_FUNC(Peer::rb_erase), 0); rb_define_const(rb_cPeer, "DISABLE", INT2NUM(0)); rb_define_const(rb_cPeer, "ENABLE", INT2NUM(1)); }
(続く)