Perl5 Shadow Classes (highly experimental, but cool) October 23, 1996 1. Introduction ---------------- This release of SWIG now supports the concept of "shadow classing" in Perl5. What is shadow classing you ask? In a nutshell, shadow classing attempts to create Perl classes and objects that behave exactly like you would expect, except that they really mirror (or shadow) real C/C++ data structures. As a result, you get a really nice interface to your C program. This implementation would not be possible without the generous contributions of David Fletcher and Gary Holt who independently provided radically different prototype implementations. I have combined ideas from both approaches and generalized the Perl5 module in an attempt to solve some of the more subtle problems. 2. Implementation details -------------------------- The shadow classing mechanism is built using a combination of C and Perl. SWIG's low-level C interface is used at the lowest level while Perl is used to provide the high-level structure. While, the C interface is essentially the same as before, you can look at the Perl support code by simply loading the resulting '.pm' file into your editor. Since it's written in Perl, it's compact, and easily customizable without having to recompile the C module. 3. Invocation -------------- To invoke SWIG's shadow class support, run the Perl5 module with the '-shadow' option. SWIG will produce two files as before--a .c file and a .pm file. However, in this case, the .pm file will contain a collection of supporting Perl code. The shadowing option results in two packages. The low-level interface is managed in a package 'packagec' where 'package' is the name you gave to the package (The 'c' is appended onto the end). A cleaned up package is found in 'package'. This package contains fixed method invocations to take care of Perl objects and classes before calling the lower level interface. If you're completely confused, don't worry--it's not as weird as it sounds (well, hopefully it's not). 4. Example : ------------- Let's say you have the following interface file : %module vector struct Vector { double x, y, z; Vector(); ~Vector(); void print(); }; Vector add(Vector a, Vector b); %include perlmain.i // Build a static version of Perl You could wrap this with SWIG as follows : % swig -perl5 -shadow -static vector.i % # compile it into a program named 'vecperl' After compilation, you will end up with a new version of Perl (or a shared library if you're using dynamic loading) and a file 'vector.pm'. Now, we could use our new module as follows : #!/vecperl # Test our new version of Perl out use vector; #Load our module # Create a new vector $v = Vector->new(); $v->{x} = 2.5; $v->{y} = -1.0; $v->{z} = 7.0; $v->print(); # Print it out # Create another vector using different syntax $w = Vector::new(); %$w = ( x => 3.0, y => 9.0, z => -2.5 ); # Create yet another vector using yet another syntax $u = new Vector; %$u = ( x => 0.0, y => 5.0, z => 0.0 ); # Add some vectors together $z = vector::add($v,vector::add($w,$u)); $z->print(); Now, let's say we wanted to make a more sophisticated structure like this : struct Particle { Particle(); ~Particle(); Vector r; Vector v; Vector f; int type; }; We can use it in an entirely natural way in Perl : #!vecperl $p = new Particle; %{$p->{r}} = ( x=> 0.0, y => 0.0, z=> 0.0); # Set 'r' %{$p->{v}} = ( x=> 0.0, y => 0.0, z=> 0.0); # Set 'v' %{$p->{f}} = ( x=> 0.0, y => 0.0, z=> 0.0); # set 'f' $p->{type} = 1; # Print out the components $p->{r}->print(); $p->{v}->print(); $P->{f}->print(); Simple public inheritance is also support in a natural manner. (Multiple inheritance is not currently supported). 5. The Gory Details --------------------- So now that you've pondered the above example, you may have a few questions : 1. How are classes implemented? 2. How are member attributes handled? 3. What is an "object?" 4. How are function arguments handled? (and how do stand-alone functions like add() above work?) 5. Who controls what? (or who owns what?). How are classes implemented ? : Classes are represented as Perl packages as described in chapter 5 or "Programming Perl (2nd Ed.)", by Wall, Christiansen, and Scwartz. The individual methods are simply wrappers around the low-level SWIG interface. While one might ask why it's necessary to have these extra wrappers, it turns out that it is sometimes necessary to munge function arguments before calling a C function so having this in Perl makes it quite convenient. Inheritance is supported using the Perl @ISA array. As far as I know it works in a completely straightforward manner. How are member attributes handled ? : Member attributes are implemented using tied-hash tables. Different data items are accessed as if they were in a hash table while all class methods can still be applied normally. This tied-hash interface is somewhat magical but seems to work. Even classes that have no data members are represented as tied-hashes. There are reasons for this.... What is an "object?" : When you invoke a constructor or get an object back, you will get a tied-hash table returned to you. While this behaves alot like a Perl5 scalar object, it's really quite different. If you want to access the low-level scalar object returned by SWIG, you can do it as follows : $v = new Vector; $vscalar = tied(%$v); How are function arguments handled ? : In our example, we had the following function : Vector add(Vector a, Vector b); If any of a function's arguments match the definition of an object that we have wrapped into a Perl class (or package, or whatever), then SWIG will expect to received a tied-hash table as input. Similarly, if the return result is a complex data type corresponding to a Perl class, SWIG will make this function return a tied-hash table representation. Any function arguments not matching a Perl class are simply mapped into a normal SWIG argument type like before. Thus, with this scheme, we can invoke the add() function by simply passing the objects returned by the Vector constructor : $v = new Vector; $w = new Vector; $z = vector::add($v,$w); If you're scratching your head wondering about how the "Vector" type is used in the function call, refer to the SWIG users guide and the section on 'Complex Datatypes.' However, to make a long story short, the add() function above results in an implicit creation of a new vector to hold the result. Since SWIG only knows about pointers to this, a return type of "Vector" makes no sense. Therefore, SWIG calls the function like this : Vector *wrap_add(Vector *a, Vector *b) { Vector *result; result = (Vector *) malloc(sizeof(Vector)); *result = add(*a,*b); return return; } Upon first glance, you immediately notice a huge memory leak problem which brings us to the last point... Who owns what? SWIG needs to manipulate objects that are both created in Perl and C/C++. However, if a Perl class has a destructor in it, then Perl will try to destroy every object when it is no longer needed. Obviously, this is a problem if the object was really created in C/C++ and is being used for some other thing inside the C code (having Perl going around arbitrarily destroying things is probably a bad idea). To solve these problems, SWIG applies the following rules : 1. If Perl created the object, then Perl can destroy it 2. If SWIG created the object (perhaps implicitly like in the add() function), then Perl owns the object and can destroy it as well. 3. Otherwise, Perl is not allowed to destroy the object. To illustrate how this works, consider two different functions : Vector add(Vector a, Vector b) { Vector result; result.x = a.x + b.x; result.y = a.y + b.y; result.z = a.z + b.z; } Vector *get(Vector *a, int i) { return a+i; } The function add() adds two vectors but results in an implicit malloc() when SWIG returns the result. The get() function simply returns a pointer to a vector in an array. Now in Perl, $a = add($x,$y); # Perl now owns the result $a # Since perl owns the result of add(), it can be deleted. # This makes the following code have no memory leaks : $a = add(add(add($x,$y),$y),$y); # There are no memory leaks in this code since Perl owns # the intermediate results and can clean up as necessary. # Now look at this : for ($i = 0; $i < 100; $i++) { $a = get($x,$i); $a->print(); } # This code loops over an array of 100 vectors and prints # them out. In this case, the return value of get() # is owned by C (there is no way for Perl to know where # the pointer is coming from). Every time $a is reassigned, # Perl destroys the old value. However, since Perl doesn't # own this vector (by our rules), the actual Vector is not # destroyed. 6. Examples ------------- The "shadow" directory contains a number of shadow-class examples for Perl5. Hopefully this is enough to get you going. 7. Disclaimer -------------- This modification is highly experimental. I believe that it works, but if you find any errors, please post a message to swig@cs.utah.edu.