C++ from Ruby ARRRGGHH!!

A

Asfand Yar Qazi

Yes, I know there's a wrapper available already that can wrap C++
exceptions in Ruby, but I want to do it myself as a learning exercise.

Anyway, could you look at the following test code that reveals the
problem? I just don't know what to do! Are C++ exceptions in GCC
incompatible with Ruby exceptions? I think that's the problem, isn't it...

(I have put a link to an archive with these files, plus the library
binary, here: http://users.wmin.ac.uk/~w9921783/ruby-test.tar.gz)

=====file Makefile
Test.so: ruby-test.cc
g++ -shared -o $@ -I`ruby -rrbconfig \
-e 'puts Config::CONFIG["archdir"]'` $<

=====file t.rb
#!/usr/local/bin/ruby -w

require 'Test'

include Mod

Test.init
(1..20).each() do
begin
Test.init
rescue CPP_Error => e
puts("C++ exception '#{e.cppclass}' occured, message = \"#{e.message}\"");
rescue Exception => e
puts("Exception #{e.class.name} occured: #{e.message}");
end
end

Test.deinit

=====file ruby-test.cc

#include "ruby.h"
#include <stdexcept>
#include <string>
#include <typeinfo>

namespace
{

struct Test
{
static bool init_;

static void
init()
{
if(!init_)
init_ = true;
else
throw std::runtime_error("DDStuff");
}

static bool
is_init() {return init_;}

static void
deinit() {init_ = false;}
};

bool Test::init_ = false;

VALUE cTest;

/// Module
VALUE mMod;

/// C++ exception
VALUE eCPP_Error;

/// Define a class (as rb_define_class) under mMod
inline VALUE defclass(const char *name, VALUE super)
{
return rb_define_class_under(mMod, name, super);
}

/// Convert boolean to Qtrue/Qfalse
inline VALUE convbool(bool arg) {if(arg)return Qtrue; else return Qfalse;}

/// Takes an exception 'e', gets its name and 'what' message, and uses
/// it to throw a Ruby exception
void
raise_ruby(std::exception& e)
{
std::string classname = typeid(e).name();

/*
COMMENT: This gives the error message show in Init_Test

*/
// VALUE excep = rb_funcall(eCPP_Error, rb_intern("new"), 1,
// rb_str_new2(classname.c_str()));
// rb_raise(excep, e.what());



// COMMENT: WITH THE FOLLOWING instead of the above, when Test.init
// has been called a certain number of times, a segfault occurs

std::string eval_string = "raise CPP_Error.new('" + classname
+ "'), '" + e.what() + "'";
rb_eval_string(eval_string.c_str());
}

/// @define CHECK_CPP Call given C++ code, and check it for
/// exceptions, throwing if necessary
#define CHECK_CPP(code) try {code;} catch(std::exception& e)
{raise_ruby(e);}

// Test
VALUE
Test__init(VALUE self)
{
CHECK_CPP(Test::init());
return self;
}

VALUE
Test__is_init(VALUE self)
{
return convbool(Test::is_init());
}

VALUE
Test__deinit(VALUE self)
{
Test::deinit();
return self;
}

// Call with 'cppclass'
VALUE
CPP_Error__initialize(int argc, VALUE* argv, VALUE self)
{
VALUE argcppclass;
VALUE argmessage;

if(rb_scan_args(argc, argv, "12", &argcppclass, &argmessage) == 1)
argmessage = rb_str_new2("");
StringValue(argcppclass);
rb_call_super(1, &argmessage);
rb_iv_set(self, "@cppclass", argcppclass);

return self;
}

VALUE
test(...)
{
// VALUE retval = INT2FIX(0);
// StringValue(retval);
// return retval;

VALUE tmp = rb_funcall(eCPP_Error, rb_intern("new"),
1, rb_str_new2("AnException"));
return tmp;

// rb_funcall(rb_mKernel, rb_intern("p"), 1,
// rb_funcall(eCPP_Error, rb_intern("inspect"), 0));
// return rb_funcall(eCPP_Error, rb_intern("inspect"), 0);
// return eCPP_Error;
}

} // namespace

typedef VALUE(*rbfunk)(...);

extern "C" void Init_Test()
{
// module Mod
mMod = rb_define_module("Mod");

rb_define_global_function("testsr", test, 0);

/*
COMMENT: Using raise-ruby.c block 1, regardless of which of the
following styles I try, I get the following:

../t.rb:10:in `init': undefined method `new' for
Mod::CPP_Error:Mod::CPP_Error (NoMethodError)
from ./t.rb:10
from ./t.rb:8:in `each'
from ./t.rb:8

However, see the 'raise_ruby' func above...
*/

// STYLE1
eCPP_Error = defclass("CPP_Error", rb_eRuntimeError);
rb_define_method(eCPP_Error, "initialize",
(rbfunk)CPP_Error__initialize, -1);
rb_eval_string("class Mod::CPP_Error; attr_reader:)cppclass);end");
// END STYLE1

// STYLE2
// rb_eval_string("module Mod\n"
// " class CPP_Error < RuntimeError\n"
// " def initialize(cppclass, message = '')\n"
// " super(message)\n"
// " @cppclass = cppclass\n"
// " end\n"
// " attr_reader:)cppclass)\n"
// " end\n"
// "end\n");
// eCPP_Error = rb_eval_string("Mod::CPP_Error");
// STYLE2

cTest = defclass("Test", rb_cObject);
rb_define_singleton_method(cTest, "init",
(rbfunk)Test__init, 0);
rb_define_singleton_method(cTest, "is_init?",
(rbfunk)Test__is_init, 0);
rb_define_singleton_method(cTest, "deinit",
(rbfunk)Test__deinit, 0);
}
 
N

nobu.nokada

Hi,

At Sun, 18 Jan 2004 16:20:01 +0900,
Asfand said:
Anyway, could you look at the following test code that reveals the
problem? I just don't know what to do! Are C++ exceptions in GCC
incompatible with Ruby exceptions? I think that's the problem, isn't it...

Yes, of course, incompatible. They aren't concerned with at
all. You have to protect or wrap each exceptions at every
boundaries.
 
A

Asfand Yar Qazi

Hi,

At Sun, 18 Jan 2004 16:20:01 +0900,



Yes, of course, incompatible. They aren't concerned with at
all. You have to protect or wrap each exceptions at every
boundaries.

As you can see from the code...

#define CHECK_CPP(code) try {code;} \
catch(std::exception& e) {raise_ruby(e);}

i.e. catch a C++ exception, wrap its contents in a Ruby exception,
dispatch that Ruby exception. But that corrupts the program stack I
think, resulting in really funny and annoying errors (as documented in
the OP), and eventually resulting in a coredump. Its all due to the
setjmp/longjmp used by Ruby exceptions and GCC's advanced
exception-handling mechanism, which are incompatible.

I wait for the day when a scripting language will be written in C++, and
be fully compatible will C++ features (perhaps even with templates.)
But still, I'll just redo my C++ code in a Ruby-esque way, i.e. throwing
Ruby exceptions instead of C++ exceptions, so no problems.
 
N

nobu.nokada

Hi,

At Sun, 18 Jan 2004 16:20:01 +0900,
Asfand said:
if(rb_scan_args(argc, argv, "12", &argcppclass, &argmessage) == 1)

This must be "11", numbers of mandatory arguments and optional
arguments, second is not the total.
 
A

Asfand Yar Qazi

Hi,

At Sun, 18 Jan 2004 16:20:01 +0900,



This must be "11", numbers of mandatory arguments and optional
arguments, second is not the total.

Ah... one day, someone should do a rewrite on README.EXT :)

That said, I used another method and it didn't do anything differently,
so this can't have been the problem.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top