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

(続く)