From: Paul Brannan Date: 2002-11-09T04:11:16+09:00 Subject: Re: Extending ruby with mixed C/ruby classes? On Sat, Nov 09, 2002 at 03:33:37AM +0900, Eric Schwartz wrote: > I'd like to extend Ruby to directly interface with STAF > (http://staf.sf.net/), which I'm using to actually execute my tests, > and report on their status. STAF has a C API, and I think I > understand how to wrap it with Ruby, but I have a few questions on > that. I've never heard of STAF until now, but it looks intriguing. What would STAF give you that Test::Unit would not? > Just FYI, the only reference I've found so far is the online Pickaxe; > other pointers are welcome, so long as they are available online > (offline refs are welcome too, but I'd like to finish this today if > possible; it doesn't seem to be THAT hard, and I'm getting excited > enough about Ruby that I want to start USING it, darn it!). The best source of information that I've found is the Ruby source itself. I'm constantly finding new things that I didn't know existed, and discovering new functions and techniques that aren't in the Pickaxe. The second best source of information I know of is this list. Lots of people really know what they are talking about and are willing and capable of answering questions. I wish there were a better reference for the C API, but I don't know of one. [ruby-talk:45142] seems to indicate that in the future rdoc will be used to document the API. > Also, I would like to define a few Ruby classes as well, to make > exception handling a little easier. > > I've included a sort of psuedocode for how I'd write the whole thing > in ruby below. The issues I have are: > > 1) I'm reading the Pickaxe example, and it seems to match my situation > quit nicely-- STAF has a STAFHandle_t type that connects the client > with the server, and is passed as a parameter to all other calls. > So I want to wrap that up in a STAFHandle class (I'm naming the > class that way to match the C++ and Java interfaces; I'm going to > compose that into a STAFCommand class that operates like a File). > > So far, so good, but when I read the Pickaxe, it shows the CDPlayer > class defining an 'initialize' method (good, I get that) and a > 'new' Singleton method. What?!? I'm perfectly willing to concede > that I don't understand Ruby's concept of Singleton methods, but > why doesn't the example just create the CDJukebox * and call > Data_Wrap_Struct() on it in the initialize() function? Why do it > that way? And, of course, do I need to mirror that structure in my > code? initialize() doesn't create a new object; it initializes an already created object. The singleton method new creates the object and calls initialize(). C++ works similarly; operator new creates new objects on the heap and makes a call to the constructor; the constructor then initializes the object. > 2) How do I structure my code so that I can keep STAFResult and > STAFException as pure Ruby classes, and just create and/or > raise them from the STAFHandle class? I saw all sorts of > examples of how to create Strings, Arrays, and Hashes, in C code, > but nothing about how to create some generic Ruby class in a C > method. The easiest solution is probably to call rb_require() to load your Ruby code from Init_STAF(): static VALUE rb_eSTAFException; static VALUE rb_cSTAFResult; #ifdef __cplusplus extern "C" { #endif void Init_STAF() { /* ... */ rb_require("STAFException.rb"); rb_require("STAFResult.rb"); rb_eSTAFException = rb_const_get(rb_cObject, rb_intern("STAFException"); rb_cSTAFResult = rb_const_get(rb_cObject, rb_intern("STAFException"); /* ... */ } #ifdef __cplusplus } #endif You can then raise a STAFException with: rb_raise(rb_eSTAFException, "your message here"); Be sure not to call rb_raise if you have created any C++ objects on the stack that still need to be destroyed! Those objects may or may not get properly cleaned up! (to make a long story short... :) You can create a STAFResult object with: VALUE args[] = {}; VALUE result = rb_class_new_instance( sizeof(args)/sizeof(VALUE), /* argument count */ args, /* the arguments themselves */ rb_cSTAFResult); which will create a new STAFResult object, call its initialize() method with the arguments you passed in, and return the object back to you. > Assuming I have STAFHandle.c, STAFResult.rb and STAFException.rb > in the same directory, how can I create a STAFResult object in > STAFHandle.c? Am I correct in assuming that I'd use the same > method to create a STAFException (or some appropriate subclass), > and then call 'rb_raise(cExceptionInstance, "Appropriate text")'? rb_raise takes a class as its first argument, not an instance. If you want to raise an instance, then use rb_exc_raise. > Thanks for your help-- I had initially thought Ruby a solution in > search of a problem, but all of a sudden, everything's starting to > look OO again, and it's a happy feeling. Any help you can give in > getting me out of this quandry is greatly appreciated. > > -=Eric Paul