On 4/7/07, Noah Easterly <noah.easterly / gmail.com> wrote:
> On Apr 7, 1:08 pm, "Kent Sibilev" <ksr... / gmail.com> wrote:
> > You should wrap your rb_yield call with rb_ensure. From README.EXT:
> >
> >  VALUE rb_ensure(VALUE (*func1)(), void *arg1, void (*func2)(), void *arg2)
> >
> > Calls the function func1 with arg1 as the argument, then calls func2
> > with arg2 if execution terminated.  The return value from
> > rb_ensure() is that of func1.
>
> Hmm.... this doesn't seem to have the desired effect.  I'm pretty sure
> rb_ensure() is just for handling exceptions, not breaks.
>
> According to ruby.h and the Pickaxe, the function def'n has changed a
> bit too.  From Programming Ruby(2nd ed):
>
>  VALUE rb_ensure(VALUE(*body)(), VALUE args, VALUE(*ensure)(), VALUE
> eargs)
>
> Executes body with the given args.  Whether or not an exception is
> raised, execute ensure with the given eargs after body has completed.
>
> So it seems that rb_ensure is just for exceptions, not 'break'.  I
> tried tweaking my code to use it anyway, but the ensure call is still
> bypassed when there is a break.  For example:
>
> >>> lib.c
> void func( int n,  int (*callback)(void) )
> {
>   printf("before callback...\n");
>   if (callback() < 0)
>     goto cleanup;
>   printf("after callback...\n");
> cleanup:
>   printf("in cleanup...\n");
> }
> >>> ext.c
> static void
> my_ensure(void * ptr)
> {
>   *(int *)ptr = -1;
> }
> static int
> meth_callback(void)
> {
>   int retval = 0;
>   rb_ensure(rb_yield, Qnil, my_ensure, &retval);
>   return retval;
> }
> // ...
> >>> test.rb
>
> require 'ext'
> YYY.new.meth(100) { } # outputs "before callback...\n", "in cleanup...
> \n"
> YYY.new.meth(100) { break } # outputs "before callback...\n"
>
> Which makes sense if, when there is no break, the my_ensure func gets
> called (as it should), so processing skips to cleanup, and when there
> is a break, the ensure func does not get called, and processing skips
> back to the ruby script.
>
> I suppose I could switch to having people 'raise BreakException',
> rather than 'break' within the block, but that seems like its hobbling
> the ruby language.
>
> Thanks for the idea, though!

It sounds like you want to do the equivalent of turning the given
block into a lambda.  The break is causing a return from the block.

Here's some ruby code which shows the problem and the solution:

rick@frodo:/public/rubyscripts$ cat lambda.rb
def with_yield
  puts "before yield"
  yield
  puts "got back"
end

def with_call(&block)
  puts "before call"
  block.call
  puts "got back"
end

def with_lambda(&block)
  puts "before lambda call"
  (lambda &block).call
  puts "got back"
end

with_yield {puts "in block"; break}
puts
with_call {puts "in block"; break}
puts
with_lambda {puts "in block"; break}
rick@frodo:/public/rubyscripts$ ruby lambda.rb
before yield
in block

before call
in block

before lambda call
in block
got back


Now just turn that into C code.

You need rb_scan_args with a format of "&" on the last argument to get
the block. rb_funcall2 to call the lambda method which is private

And you probably also want to use rb_ensure in case the block raises
and exception as well.

-- 
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/