From: quixoticsycophant@... (Jeff Mitchell) Date: 2004-05-14T16:43:54+09:00 Subject: ruby C++ extensions The recent C++/ruby post has reminded me to say a few words about my travails in ruby C++ extensions, if only to possibly help some poor soul googling in the future. First, require 'mkmf';create_makefile("cxxruby") will use CONFIG['LDSHARED'] to create your extension, which is most likely wrong. For gcc this results in the error undefined symbol: __gxx_personality_v0 when you attempt to load the module. The linker must be C++ aware due to static C++ objects: exceptions, iostream, rtti, etc. I solved this by setting CONFIG['LDSHARED'] = "g++ -shared". This solution is nonportable -- perhaps someone can suggest a better way? To be consistent, ruby could detect the presence/absence of a C++ compiler during ./configure, setting CONFIG['CXX'] and CONFIG['CXX_LDSHARED']. Also, mkmf should know to use CXX_LDSHARED when C++ sources exist. Second, ruby's exception handling via setjmp/longjmp effectively means you should never construct a C++ object with a nontrivial destructor on the stack. If ruby longjmps out of your code, your destructors will not be called. Therefore if a ruby method you are implementing requires the use of temporary C++ objects, you must construct those objects on the heap. The easiest and saftest technique I found is to write three C functions for a ruby method: the actual ruby hook, the body, and the ensure: #include "ruby.h" class CXXRuby { } ; static VALUE rb_cxxruby_superfunk_body( void** args ) ; static VALUE rb_cxxruby_superfunk_ensure( void** args ) ; static VALUE rb_cxxruby_superfunk( int argc, VALUE* argv, VALUE self ) { // construct all C++ objects on the heap CXXRuby* cxx_obj = 0 ; void* args[4] ; args[0] = reinterpret_cast(argc) ; args[1] = static_cast(argv) ; args[2] = reinterpret_cast(self) ; args[3] = static_cast(&cxx_obj) ; try { cxx_obj = new CXXRuby() ; } catch(...) { rb_cxxruby_superfunk_ensure(args) ; rb_raise(rb_eRuntimeError, "caught exception from an unfunky constructor") ; } return rb_ensure( reinterpret_cast(rb_cxxruby_superfunk_body), reinterpret_cast(args), reinterpret_cast(rb_cxxruby_superfunk_ensure), reinterpret_cast(args)) ; } static VALUE rb_cxxruby_superfunk_body( void** args ) { int argc = reinterpret_cast(args[0]) ; VALUE* argv = static_cast(args[1]) ; VALUE self = reinterpret_cast(args[2]) ; CXXRuby* cxx_obj = *static_cast(args[3]) ; try { VALUE get_out ; VALUE the_funk ; if( rb_scan_args(argc, argv, "11", &get_out, &the_funk) == 1 ) { the_funk = Qtrue ; } // ... } catch(...) { rb_raise(rb_eRuntimeError, "insufficient funk levels") ; } return self ; } static VALUE rb_cxxruby_superfunk_ensure( void** args ) { // C++ cleanup CXXRuby* cxx_obj = *static_cast(args[3]) ; delete cxx_obj ; return Qnil ; } static VALUE cCXXRuby ; extern "C" { void Init_cxxruby() { cCXXRuby = rb_define_class("CXXRuby", rb_cObject) ; rb_define_singleton_method( cCXXRuby, "superfunk", reinterpret_cast(rb_cxxruby_superfunk), -1) ; rb_define_global_const("OperationSupergroove", cCXXRuby) ; } } // extern "C"