J
Jeff Mitchell
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<void*>(argc) ;
args[1] = static_cast<void*>(argv) ;
args[2] = reinterpret_cast<void*>(self) ;
args[3] = static_cast<void*>(&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<VALUE(*)(...)>(rb_cxxruby_superfunk_body),
reinterpret_cast<VALUE>(args),
reinterpret_cast<VALUE(*)(...)>(rb_cxxruby_superfunk_ensure),
reinterpret_cast<VALUE>(args)) ;
}
static VALUE rb_cxxruby_superfunk_body( void** args )
{
int argc = reinterpret_cast<int>(args[0]) ;
VALUE* argv = static_cast<VALUE*>(args[1]) ;
VALUE self = reinterpret_cast<VALUE>(args[2]) ;
CXXRuby* cxx_obj = *static_cast<CXXRuby**>(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<CXXRuby**>(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<VALUE(*)(...)>(rb_cxxruby_superfunk),
-1) ;
rb_define_global_const("OperationSupergroove", cCXXRuby) ;
}
} // extern "C"
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<void*>(argc) ;
args[1] = static_cast<void*>(argv) ;
args[2] = reinterpret_cast<void*>(self) ;
args[3] = static_cast<void*>(&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<VALUE(*)(...)>(rb_cxxruby_superfunk_body),
reinterpret_cast<VALUE>(args),
reinterpret_cast<VALUE(*)(...)>(rb_cxxruby_superfunk_ensure),
reinterpret_cast<VALUE>(args)) ;
}
static VALUE rb_cxxruby_superfunk_body( void** args )
{
int argc = reinterpret_cast<int>(args[0]) ;
VALUE* argv = static_cast<VALUE*>(args[1]) ;
VALUE self = reinterpret_cast<VALUE>(args[2]) ;
CXXRuby* cxx_obj = *static_cast<CXXRuby**>(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<CXXRuby**>(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<VALUE(*)(...)>(rb_cxxruby_superfunk),
-1) ;
rb_define_global_const("OperationSupergroove", cCXXRuby) ;
}
} // extern "C"