SWIG & the New Allocation Framework (Ruby 1.7)


(Lyle Johnson) #1

All,

I’m trying to decide what changes (if any) should be made to the wrapper
code generated by SWIG in light of the new allocation framework in Ruby
1.7. I should preface this by saying that the current SWIG/Ruby
implementation (from SWIG 1.3.12) seems to be working just fine with
Ruby 1.7 and so I’m tempted to just leave it alone. But if there are
some demonstrable benefits of switching over I’d of course do so. (I’ve
found a few threads in the ruby-talk archives about the implications of
the change for extension writers, but I have yet to find any discussion
of why this was changed :wink:

The current implementation works as follows. When SWIG sees a C++ class
declaration like so:

 class Foo {
 public:
     Foo();
 };

it defines a new class named Foo, with a “new” singleton method and an
"initialize" instance method. The C code for the new singleton method is
pretty boilerplate and looks something like this:

 static VALUE
 _wrap_Foo_new(int argc, VALUE *argv, VALUE klass) {
     VALUE obj = Data_Wrap_Struct(klass, markfunc, freefunc, NULL);
     rb_obj_call_init(obj, argc, argv);
     return obj;
 }

The C code for the initialize instance method looks like something this:

 static VALUE
 _wrap_Foo_initialize(int argc, VALUE *argv, VALUE self) {
     Foo *foo = new Foo();
     DATA_PTR(self) = foo
     return self;
 }

This arrangement works well; users can subclass Foo in their Ruby code
and override its initialize method as usual, e.g.

 class MyFoo < Foo
   def initialize(my_stuff)
     super()
     @my_stuff = my_stuff
   end
 end

My take on the new allocation framework, based on what I’ve seen so far,
boils down to the following change: we shouldn’t override Foo.new
anymore, but instead provide a Foo.allocate singleton method whose C
implementation would look like this:

 static VALUE
 _wrap_Foo_allocate(VALUE klass) {
     return Data_Wrap_Struct(klass, markfunc, freefunc, NULL);
 }

It looks like the implementation of Foo#initialize should remain the
same. Is there more to it than that? Is there some benefit (within the
context of SWIG/Ruby) for me changing this or should I just leave things
as-is?

Thanks in advance for any enlightenment on this topic :wink:

Lyle


(ts) #2

It looks like the implementation of Foo#initialize should remain the
same. Is there more to it than that?

If you do this all method call must be protected to be sure that
#initialize was called. This is what ruby do, for example, with File

pigeon% ruby -e 'a = File.allocate; a.read'
-e:1:in `read': uninitialized stream (IOError)
        from -e:1
pigeon%

Guy Decoux


(Lyle Johnson) #3

It looks like the implementation of Foo#initialize should remain the
same. Is there more to it than that?

If you do this all method call must be protected to be sure that
#initialize was called. This is what ruby do, for example, with File

pigeon% ruby -e ‘a = File.allocate; a.read’
-e:1:in `read’: uninitialized stream (IOError)
from -e:1

Of course you’re correct, but is it intended that the user will call
Foo.allocate directly (as your example shows)? I was under the impression
that code would continue to call Foo.new, which has the following
consequences:

  1. Calling Foo.new results in a call to rb_class_new_instance();
  2. rb_class_new_instance() first calls rb_obj_alloc(), resulting in a call
    to Foo.allocate;
  3. rb_class_new_instance() follows this up with a call to
    rb_call_obj_init(), resulting in a call to Foo#initialize.

As I said in my original post, I’m not entirely clear on the motivations for
this change and so perhaps it’s intended that the user will also call
Foo.allocate from Ruby code. And if so, I agree that they’d better make sure
to initialize the new object before using it!

Thanks,

Lyle


(ts) #4

Of course you're correct, but is it intended that the user will call
Foo.allocate *directly* (as your example shows)?

Yes, some users can find usefull to use ::allocate

Guy Decoux


(Christoph) #5

“Lyle Johnson” jlj@cfdrc.com wrote in message
news:577M8.18673$rX.3117253@e420r-atl1.usenetserver.com

It looks like the implementation of Foo#initialize should remain the
same. Is there more to it than that?

If you do this all method call must be protected to be sure that
#initialize was called. This is what ruby do, for example, with File

pigeon% ruby -e ‘a = File.allocate; a.read’
-e:1:in `read’: uninitialized stream (IOError)
from -e:1

Of course you’re correct, but is it intended that the user will call
Foo.allocate directly (as your example shows)? I was under the
impression
that code would continue to call Foo.new, which has the following
consequences:

You might want to call allocate to create a special instance of
your class - lets say

class Tree
Leaf = allocate # null-tree
class << Leaf
def size; 0 end
def each; Leaf end
# …
end
def initialize (data; l,r)
# …
end
def each (&b)
# …
end
end

Generally you probably don’t want to call allocate
directly - i.e. you might want to think of allocate
as a private class method. Here is semi-legitimate
example for (1.7 ruby-mswin).

···

class A
@created = 0
@finalized = 0
def initialize(data)
@data = data
end
class << A
attr_reader :created, :finalized
protected
attr_writer :created, :finalized

private
def inherited(sub_klass)
  sub_klass.created   = 0
  sub_klass.finalized = 0
end
def A.allocate
  @created+=1
  obj = super
  ObjectSpace.define_finalizer(obj,finalizer())
  obj
end
def finalizer
   proc { |id| @finalized+=1 }
end

end
end

class B < A
def initialize
end
end

A.new(1) # won’t be collected until the end
Array.new (10**1) {B.new}
proc {A.new(2)}.call
p [B.finalized,B.created, A.finalized,A.created]
GC.start
p [B.finalized,B.created, A.finalized,A.created]

resulting in

[0, 10, 0, 2]

[10, 10, 1, 2]


/Christoph