/* [Simple wrapper] standalone and less than 500 lines of code! The Rubyembed Project $Id: main.cpp,v 1.20 2003/04/17 23:14:08 neoneye Exp $ I have collected some necessary "embedding" things in one file. So that you quickly can get an impression of how things works. Because its one file (standalone), you should easily be able to compile it and test things for yourself. WARNING: this is an introduction to the [advanced wrapper] and therefore some vital/complex things has been left out. These things are: * garbage collection. * exception handling. * SWIG. The [advanced wrapper] is almost the same code as you see here, but there its splitted out over several files and therefore might look overwhelming. Get familiar with this code and you should be well prepared for the [advanced wrapper]. */ /* --------------------------- Interface --------------------------- */ class Impl; // pimpl class View { public: View(); virtual ~View(); void insert(); virtual void repaint(); private: Impl *pimpl; }; /* --------------------------- Implementation --------------------------- */ #include using std::cout; using std::endl; using std::ostream; #include #include #include #include #include /* ------------------------------------ ensure that ruby is only started/stopped once! todo: use a singleton for this ------------------------------------ */ static bool lib_running = false; void lib_begin() { if(lib_running) return; ruby_init(); ruby_init_loadpath(); lib_running = true; cout << "aeditorlib: started" << endl; } void lib_end() { if(lib_running == false) return; // ruby_run() cleans up, by invoking ruby_finalize(); ruby_finalize(); lib_running = false; cout << "aeditorlib: stopped" << endl; } namespace RUBY_CPP { using std::string; class Error : public std::exception { private: string name; string where; string message; string klass; string backtrace; Error() {} public: virtual ~Error() throw() {} virtual const char *what() const throw() { return name.c_str(); } virtual void explain(ostream &o) const throw() { o << "name=" << name.c_str() << "\n" "where=" << where.c_str() << "\n" "class=" << klass.c_str() << "\n" "message=" << message.c_str() << "\n" "backtrace=" << backtrace.c_str() << endl; } /* purpose: convert a ruby exception into c++ issues: * Is this code correct? Well.. Compare this code against ruby1.8.0/eval.c - exc_inspect(), backtrace() I think its almost correct. */ static Error Create(string name) { Error e; e.name = name; // position std::ostringstream where; where << ruby_sourcefile << ":" << ruby_sourceline; ID id = rb_frame_last_func(); if(id) { where << ":in `" << rb_id2name(id) << "'"; } e.where = where.str(); VALUE exception_instance = rb_gv_get("$!"); // class VALUE klass = rb_class_path(CLASS_OF(exception_instance)); e.klass = RSTRING(klass)->ptr; // message VALUE message = rb_obj_as_string(exception_instance); e.message = RSTRING(message)->ptr; // backtrace if(!NIL_P(ruby_errinfo)) { std::ostringstream o; VALUE ary = rb_funcall(ruby_errinfo, rb_intern("backtrace"), 0); int c; for (c=0; clen; c++) { o << "\tfrom " << RSTRING(RARRAY(ary)->ptr[c])->ptr << "\n"; } e.backtrace = o.str(); } return e; } }; struct Arguments { VALUE recv; ID id; int n; VALUE *argv; Arguments(VALUE recv, ID id, int n, VALUE *argv) : recv(recv), id(id), n(n), argv(argv) { } }; VALUE FuncallWrap(VALUE arg) { Arguments &a = *reinterpret_cast(arg); return rb_funcall2(a.recv, a.id, a.n, a.argv); } /* purpose: call a ruby function in a safe way. translate ruby errors into c++ exceptions. VALUE Unsafe() { return rb_funcall( self, rb_intern("test"), 1, INT2NUM(42) ); } VALUE Safe() { return RUBY_CPP::Funcall( self, rb_intern("test"), 1, INT2NUM(42) ); } */ VALUE Funcall(VALUE recv, ID id, int n, ...) { VALUE *argv = 0; if (n > 0) { argv = ALLOCA_N(VALUE, n); va_list ar; va_start(ar, n); int i; for(i=0;i(&arg), &state); if(state) throw Error::Create("cannot invoke ruby-function"); return result; } } // end of namespace RUBY_CPP namespace { using std::runtime_error; /* purpose: make c++ callbacks available to ruby functions: * repaint() invokes the virtual function view->repaint(). issues: * ruby-wrapper, the global_view is ugly. But unfortunatly I do not know any other ways to initialize this struct! todo: * figure out how to get ruby-garbagecollector to destroy us? */ class Redirect { private: View *view; void Init() { cout << "redirect.init: hello" << endl; } public: Redirect(View *view) : view(view) { cout << "redirect.ctor: hello" << endl; } ~Redirect() { cout << "redirect.dtor: hello" << endl; } void repaint() { cout << "redirect.repaint: enter" << endl; view->repaint(); cout << "redirect.repaint: leave" << endl; } private: /* -------------------------------- ruby wrapper -------------------------------- */ // globals is nasty static View *global_view; static void _Free(Redirect *p) { cout << "redirect::_Free: enter" << endl; delete p; cout << "redirect::_Free: leave" << endl; } static VALUE _Alloc(VALUE self) { Redirect* p = new Redirect(global_view); return Data_Wrap_Struct(self, 0, _Free, p); } static VALUE _Initialize(VALUE self) { Redirect *p; Data_Get_Struct(self, Redirect, p); p->Init(); return self; } static VALUE _Repaint(VALUE self) { Redirect *p; Data_Get_Struct(self, Redirect, p); p->repaint(); return self; } public: /* purpose: make a View class available to ruby, which contains cpp hooks. */ static void RubyInit(View *parent) { global_view = parent; // nasty VALUE h = rb_define_class("RubyView", rb_cObject); rb_define_alloc_func(h, Redirect::_Alloc); typedef VALUE (*HOOK)(...); rb_define_method(h, "initialize", reinterpret_cast(&Redirect::_Initialize), 0); rb_define_method(h, "repaint", reinterpret_cast(&Redirect::_Repaint), 0); } }; View *Redirect::global_view = 0; } // end of anonymous namespace /* purpose: an instance of the ruby class "View" functions: * invoke functions on the instance. todo: * figure out how to get ruby-garbagecollector to destroy us? */ class Impl { private: View *parent; VALUE self; public: Impl(View *parent) : parent(parent) { // define class View Redirect::RubyInit(parent); /* load ruby script "test.rb" and create an instance of the class "RubyView". */ self = Qnil; int state = 0; rb_protect(Impl::Init, reinterpret_cast(this), &state); if(state) throw RUBY_CPP::Error::Create("error loading test.rb"); } ~Impl() { } void insert() { RUBY_CPP::Funcall(self, rb_intern("insert"), 0); } private: static VALUE Init(VALUE arg) { Impl *view = reinterpret_cast(arg); rb_require("test"); // create an instance of RubyView VALUE klass = rb_const_get(rb_cObject, rb_intern("RubyView")); VALUE self = rb_class_new_instance(0, 0, klass); rb_gv_set("view", self); view->self = self; return Qnil; } }; View::View() { cout << "view.ctor: enter" << endl; pimpl = new Impl(this); cout << "view.ctor: leave" << endl; } View::~View() { cout << "view.dtor: enter" << endl; delete pimpl; cout << "view.dtor: leave" << endl; } void View::insert() { cout << "view.insert: enter" << endl; pimpl->insert(); cout << "view.insert: leave" << endl; } void View::repaint() { cout << "view.repaint: hello" << endl; } /* --------------------------- Test --------------------------- */ class ViewQT : public View { public: void repaint() { cout << "viewqt.repaint: bingo" << endl; } }; int Test() { try { ViewQT v; v.insert(); return 0; } catch(const RUBY_CPP::Error &e) { std::cerr << "EXCEPTION (RUBY):" << endl; e.explain(std::cerr); return 1; } catch(const std::exception &e) { std::cerr << "EXCEPTION=" << e.what() << endl; return 1; } } int main() { cout << "main: enter" << endl; lib_begin(); int status = Test(); lib_end(); cout << "main: leave (" << status << ")" << endl; return status; }