C++ で Ruby 拡張ライブラリ ( Data_Wrap_Struct とか )
とりあえず以下ダラダラのアウトプットはこちら
gist に置いといた https://gist.github.com/1437782
作りたいもの
- 僕は神になる。ヒト(クラス)を作ってみるぞ
- 人とは、名前と年齢を持っている存在である
- 人とは、名前と年齢を伝える為、ちゃんとご挨拶ができる存在である
作り方とか
C++ でヒトクラスを作る
human.hxx
/* * human.hxx */ #ifndef _INCLUDE_HUMAN_H_ #define _INCLUDE_HUMAN_H_ #include <string> #include <sstream> class Human { private: static const int _signature = 0x123f3f7c; public: Human(const char* name, int age); virtual ~Human() {} bool isLegal() const { return this->_sig == _signature; } std::string toString() const; std::string greet() const; private: int _sig; std::string _name; int _age; }; #endif // _INCLUDE_HUMAN_H_
human.cxx
/* * human.cxx */ #include "human.hxx" Human::Human(const char* name, int age) { this->_sig = _signature; this->_name = name; this->_age = age; } std::string Human::toString() const { std::ostringstream ret; ret << "Human [name=" << this->_name << ", age=" << this->_age << "]"; return ret.str(); } std::string Human::greet() const { std::ostringstream ret; ret << "My name is " << this->_name << ". I'm " << this->_age << " years old."; return ret.str(); }
C++ 製ヒトクラスはこんな感じで使える
#include <iostream> #include "human.hxx" void show(Human* p) { std::cout << p << std::endl; if (p->isLegal()) { // 正しいオブジェクトの場合のみ出力する std::cout << "\t" << p->toString() << std::endl << "\t" << p->greet() << std::endl; } } int main() { Human h("hamajyotan", 18); // ごく普通にインスタンス生成 show(&h); Human* p; // 初期化されていないポインタを与える show(p); // この場合、アドレスのみしか出力されない p = new Human("foo", 20); // new でインスタンス生成 show(p); delete p; return 0; }
実行結果例。 2回目の show 呼び出しではアドレスしか出力されていない
$ ./a.out 0xbfcc104c Human [name=hamajyotan, age=18] My name is hamajyotan. I'm 18 years old. 0xbfcc1078 0x8d00028 Human [name=foo, age=20] My name is foo. I'm 20 years old.
C++ 製ヒトクラスを元に Ruby 拡張ライブラリを書く
humanwrap.cxx
/** * humanwrap.cxx */ #include <new> // for replacement new #include "ruby.h" #include "human.hxx" /** * Ruby 製 Human クラスのインスタンスから * C++ 製 Human クラスのインスタンスのために割り当てたメモリ領域のポインタを得ます. */ static Human* getHuman(VALUE self) { Human* p; Data_Get_Struct(self, Human, p); return p; } /** * GC によりメモリが解放される際に呼び出されます. * * wrap_Human_init で replacement new を使い * C++ 製 Human の初期化を実施するため明示的なデストラクタ呼び出しが必要になります。 * * C++ 製クラスのためにメモリ領域が割り当てられたにも関わらず、 * #initialize の引数が不正だった場合など、 C++ 製 Human のインスタンスが作成されない経路も存在します * この場合ポインタは不正なアドレスを指しているため、デストラクタを呼び出さないようにします。 */ static void wrap_Human_free(Human* p) { if (p->isLegal()) p->~Human(); ruby_xfree(p); } /** * 初期化時にメモリを割り当てます. */ static VALUE wrap_Human_alloc(VALUE klass) { // Human インスタンスのためのメモリ空間を確保し self に関連付けます // 更に、 GC が実行された際のハンドラも登録します return Data_Wrap_Struct(klass, NULL, wrap_Human_free, ruby_xmalloc(sizeof(Human))); } /** * */ static VALUE wrap_Human_init(VALUE self, VALUE _name, VALUE _age) { // 以下 2行で #initialize に渡された引数の検証を行います // 引数が正しくない場合、例外が投げられるために C++ 製 Human クラスは初期化されません。 // // この場合も GC の際に wrap_Human_free が呼ばれますが、 // wrap_Human_free に渡されるポインタは初期化されていないために不正なアドレスを指します const char* name = StringValuePtr(_name); // 文字列でなければ例外が投げられます int age = NUM2INT(_age); // 数値でなければ例外が投げられます Human* p = getHuman(self); // メモリ領域を得ます new (p) Human(name, age); // replacement new により既存のメモリ空間にインスタンスを初期化します return Qnil; // 使われません、適当な値を返しておきます } /** * toString() を実行して結果を Ruby の String クラスに変換して返します. */ static VALUE wrap_Human_toString(VALUE self) { return rb_str_new2(getHuman(self)->toString().c_str()); } /** * greet() を実行して結果を Ruby の String クラスに変換して返します. */ static VALUE wrap_Human_greet(VALUE self) { return rb_str_new2(getHuman(self)->greet().c_str()); } /** * require 'human' の際に最初に実行されます. */ extern "C" void Init_human() { // Ruby の Human クラスを定義する VALUE c = rb_define_class("Human", rb_cObject); rb_define_alloc_func(c, wrap_Human_alloc); // 初期化時にメモリを確保する関数を登録する rb_define_private_method(c, "initialize", RUBY_METHOD_FUNC(wrap_Human_init), 2); // #initialize rb_define_method(c, "inspect", RUBY_METHOD_FUNC(wrap_Human_toString), 0); // #inspect rb_define_method(c, "to_s", RUBY_METHOD_FUNC(wrap_Human_toString), 0); // #to_s rb_define_method(c, "greet", RUBY_METHOD_FUNC(wrap_Human_greet), 0); // #greet }
extconf.rb
require "mkmf" $libs += " -lstdc++ " # for STL create_makefile("human")
拡張ライブラリを作成してみる
特定のディレクトリに 4 ファイルを集めて ruby extconf.rb
そして make
human.so が出来上がる。
$ ls /home/hamajyotan/tmp/ extconf.rb human.cxx human.hxx humanwrap.cxx $ ruby extconf.rb creating Makefile $ make g++ -I. -I/usr/local/include/ruby-1.9.1/i686-linux -I/usr/local/include/ruby-1.9.1/ruby/backward -I/usr/local/include/ruby-1.9.1 -I. -D_FILE_OFFSET_BITS=64 -fPIC -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long -o humanwrap.o -c humanwrap.cxx g++ -I. -I/usr/local/include/ruby-1.9.1/i686-linux -I/usr/local/include/ruby-1.9.1/ruby/backward -I/usr/local/include/ruby-1.9.1 -I. -D_FILE_OFFSET_BITS=64 -fPIC -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long -o human.o -c human.cxx g++ -shared -o human.so humanwrap.o human.o -L. -L/usr/local/lib -Wl,-R/usr/local/lib -L. -rdynamic -Wl,-export-dynamic -lstdc++ -lpthread -lrt -ldl -lcrypt -lm -lc $ ls Makefile extconf.rb human.cxx human.hxx human.o human.so humanwrap.cxx humanwrap.o $