From: Paul Brannan Date: 2002-04-09T08:20:22+09:00 Subject: Re: Help: Ruby<->C++ callbacks On Thu, Apr 04, 2002 at 08:03:21PM +0900, Luigi Ballabio wrote: > class RubyCallback : public Callback { > public: > RubyCallback(VALUE p) > : p_(p), refCount_(new int(1)) { > /* how to make sure the Ruby proc stays alive > as long as we need it? > Is the following the right way? */ > rb_global_variable(p_); > } Why is refCount_ a pointer? > RubyCallback(const RubyCallback& o) > : p_(o.p_), refCount_(o.refCount_) { > /* how to make sure the Ruby proc stays alive > as long as we need it? */ > o.refCount_++; Here, you are incrementing o.refCount_, when you should be incrementing (*o.refCount_). > } > RubyCallback& operator=(const RubyCallback& o) { > if (refCount_ != o.refCount_) { > if (--*refCount == 0) { > // tell p_ it can go as far as we are concerned > delete refCount_; > } > p_ = o.p_; > refCount_ = o.refCount_; > // make sure p_ stays alive > *refCount_++; > } > return *this; > } > ~RubyCallback() { > if (--*refCount_ == 0) { > // now it can go as far as we are concerned > // is the following the right way? > rb_gc_mark(p_); > delete refCount_; > } > } rb_gc_mark doesn't mark an object for deletion; it marks the object as being in use. You should call this from the mark function that was passed to Data_Wrap_Struct. > void doit() { > static ID callId = rb_intern("call"); > rb_funcall(p_,callId,0); > } This is dangerous. Ruby uses longjmp() to implement exceptions. If your Ruby code throws an exception, and the function that calls doit() has an object on the stack, then that object's destructor will not get called. I do like the use of a static variable here for callId. > private: > VALUE p_; > int* refCount_; // is this needed? > }; You probably want something like this: #include #include // Hack to get this to work on gcc3 #define rb_gc_mark(value) ((void (*)(VALUE))(rb_gc_mark))(value) #if defined(RUBY_METHOD_FUNC) #undef RUBY_METHOD_FUNC #endif typedef VALUE (*RUBY_METHOD_FUNC)(); extern VALUE ruby_errinfo; struct Ruby_Exception { VALUE ex; }; class RubyCallback { public: RubyCallback(VALUE p) : p_(p) , ruby_obj_(Data_Wrap_Struct(rb_cObject, RubyCallback::mark, 0, this)) { } // the default copy constructor and assignment operator will work // making this work with an arbitrary number of arguments is left as // an excercise to the reader. VALUE call(); private: static VALUE call_ruby_proc(VALUE p) { static ID callId = rb_intern("call"); return rb_funcall(p, callId, 0); } static void mark(void * obj) { RubyCallback * rc(static_cast(obj)); rb_gc_mark(rc->p_); } private: VALUE p_; VALUE ruby_obj_; }; VALUE RubyCallback::call() { int state = 0; VALUE retval = rb_protect( RUBY_METHOD_FUNC(RubyCallback::call_ruby_proc), p_, &state); if(state != 0) { Ruby_Exception ex = { ruby_errinfo }; throw ex; } return retval; } // Some test code... #include #define RUBY_TRY \ extern VALUE ruby_errinfo; \ ruby_errinfo = Qnil; \ try #define RUBY_CATCH \ catch (Ruby_Exception & ex) { \ rb_exc_raise(ex.ex); \ } \ catch (...) \ { \ /* Can't raise the exception from here, because the C++ exception \ * won't get properly destroyed. */ \ ruby_errinfo = rb_exc_new2(rb_eRuntimeError, "Unknown error"); \ } \ if(!NIL_P(ruby_errinfo)) { \ rb_exc_raise(ruby_errinfo); \ } VALUE foo(VALUE /* self */, VALUE cb) { RUBY_TRY { struct Foo { Foo() { std::cout << "Foo" << std::endl; } ~Foo() { std::cout << "~Foo" << std::endl; } } foo; RubyCallback rc(cb); rc.call(); } RUBY_CATCH } VALUE run_test(VALUE /* self */) { // should print "Foo\nfoo!\n~Foo\n" rb_eval_string("foo(proc { puts 'foo!' })"); // should print "Foo\n~Foo\n" then print an exception msg. rb_eval_string("foo(proc { raise 'foo!' })"); return Qnil; } int main() { int argc = 3; char * argv[] = { "test", "-e" , "run_test()" }; ruby_init(); ruby_init_loadpath(); ruby_options(argc, argv); rb_define_global_function("foo", RUBY_METHOD_FUNC(foo), 1); rb_define_global_function("run_test", RUBY_METHOD_FUNC(run_test), 0); ruby_run(); } Hope this helps, Paul