From: Paul Brannan Date: 2001-12-14T05:16:01+09:00 Subject: [ruby-talk:28448] Re: ANNOUNCE: Extending Ruby With C++ Mini How-To On Fri, Dec 14, 2001 at 04:25:21AM +0900, Issac Trotts wrote: > > http://www.angelfire.com/electronic2/issac/rb_cpp_ext_tut.txt > > If anyone has comments I'd like to hear them! > > Issac Here are some comments: 1) AFAIK, the only functions you should need extern "C" in front of are the Init_xyz functions. If you need to give more functions C linkage, you might want to mention that it's possible to use extern "C" in block form: #ifdef __cplusplus extern "C" { #endif void foo(); void bar(); #ifdef __cplusplus } #endif 2) Generally speaking, ruby methods should have file scope. So either put the word "static" in front of t_init, t_add, and t_foo, or surround them with: namespace { //anon VALUE t_init(VALUE self) { ... } ... } // namespace anon the latter is more correct, as using static to indicate file scope is a deprecated feature of C++. The same rule applies to globals; VALUE cTest should probably have file scope as well. 3) Your t_foo function is not exception-safe. You need to catch C++ exceptions and re-throw them as Ruby exceptions: try { try { // do stuff that might throw an exception } catch(Exception1 & ex) { VALUE s = rb_str_new2("Exception 1 caught: "); rb_str_cat2(s, ex.reason_); throw rb_exc_new3(rb_eRuntimeError, s); } catch(std::bad_alloc & ex) { // this probably won't work :( VALUE s = rb_str_new2("std::bad_alloc"); throw rb_exc_new3(rb_eNoMemoryError, s); } } catch(VALUE ex) { rb_exc_raise(ex); return; // get rid of the compiler warning } catch(...) { rb_raise(rb_eRuntimeError, "Unknown exception"); return; // get rid of the compiler warning } I know of no good way to do this without using nested try blocks; note that when the C++ exception is caught, we re-throw it as a VALUE; when this happens; the C++ exception gets cleaned up. We then re-raise the VALUE as a Ruby exception; since VALUE is essentially a pointer, it does not need to be cleaned up. 4) You do not have the following bug in any of your examples, but it is a potential problem. If your code makes a callback to Ruby code, and the Ruby code raises an exception, then C++ objects on the stack will not be properly cleaned up. For example, if I change your t_foo function: struct Foo() { Foo() { std::cout << "Foo()" << std::endl } ~Foo() { std::cout << "~Foo()" << std::endl } }; VALUE t_foo(VALUE self) { Foo f; VALUE x = NUM2INT(Qnil); // this will raise an exception return Qnil; } Note that f will get constructed, but it may or may not get properly destructed (the C++ standard does not specify). There are a few possible solutions: 1) create f on the heap and register it with the Ruby gargage collector (this is what swig does): void delete_foo(Foo * f) { delete f; } VALUE t_foo(VALUE self) { Foo * f = new Foo; Data_Wrap_Struct(Foo, 0, delete_foo, f); VALUE x = NUM2INT(Qnil); // this will raise an exception return Qnil; } 2) Rewrite the function so that f is destructed before the exception gets thrown: VALUE t_foo(VALUE self) { { Foo f; // do something here with f } // f should now get destructed VALUE x = NUM2INT(Qnil); // this will raise an exception return Qnil; } See [ruby-talk:17943], [ruby-talk:16705], and [ruby-talk:16697] for more discussion of exceptions in C++ extensions. 5) You might also want to mention that writing Ruby extensions in C++ is non-trivial, and give some encouragement not to get discouraged. Writing correct code is a very difficult problem, and entire books have been written on the subject (see Herb Sutter's _Exceptional C++_, for a good one pertaining to exception-safe code). Also mention that SWIG (http://www.swig.org) is a good tool for eliminating many problems you might run into (though there is no such thing as a magic hammer :). Paul