[Melbourne-pm] TK repeats, scoping and keeping track of objects

Leigh Sharpe lsharpe at pacificwireless.com.au
Mon Jan 12 18:08:50 PST 2009


Hi Jacinta,


>You can create an array of all the objects and then weaken those
references so that the object will still be 
>cleaned up upon going out of scope.  You then want to make sure that
you handle holes in your array.  For 
>example:
 
Thanks, that looks like the answer I'm after.

Perhaps, however, I didn't explain my problem clearly enough:

>Is there a reason you're not returning your object?  It seems odd to
worry about whether objects can properly be 
>cleaned up when going out of scope (as you mentioned above) and then
not actually pass them back into the 
>program so that you can use them in the scope for which you've created
them.  

I am returning an object. The function I had in my original email was
not in my package, it was in main. Here's a working example of what I'm
experiencing:

in My_Package.pm:
-----
#!c:\perl\bin\perl
use strict;
use warnings;
package My::Class;

sub new
{
	my $class=shift;
# Get the class we have been called with.
	my $self={};
# Create anonymous hash to bless.
	$self->{'name'}="fred";
	$self->{'uptime'}=1;
	# And some other initialisation here.
	bless $self, $class;
# Bless the object into the correct namespace.
	return $self;
# Return the newly-blessed object.
}

sub update_status
{
	my $self=shift;		
	$self->{'uptime'}++;
	return 1;
}
sub DESTROY 
{
       my ($self) = @_;
       print $self->{name}, " is being destroyed\n";
}

1;

-----
Nothing difficult in there. Now, in my application:
----
use strict;
use warnings;
use Net::SNMP;
use Tk;
use My_Package;

my $mw=MainWindow->new(-title=>"Leigh's Test App") or die "Can't create
main window: $!\n";   # Create the main window object.
my $exit_button=$mw->Button( -command=>sub{$mw->destroy()},
-text=>"Exit");			    # Create the exit button.
my $add_button=$mw->Button(-command=>\&new_object, -text=>"Create an
object.");
$exit_button->pack(-side=>'bottom');
$add_button->pack();
MainLoop;		

sub new_object
{
 my $object=My::Class->new();           # Create a new object to monitor
 my $tl=$mw->Toplevel;                      # Create a top-level window.
 $tl->title($object->{'name'});                # Set the title of the
new window.
 $tl->Label(-text=>"Name:")->grid(-row=>0, -column=>0); # Add Labels to
window.
 $tl->Label(-textvariable=>\$object->{'name'})->grid(-row=>0,
-column=>1);
 $tl->Label(-text=>"IP Address")->grid(-row=>1, -column=>0);
 $tl->Label(-textvariable=>\$object->{'ip'})->grid(-row=>1, -column=>1);
 $tl->Label(-text=>"Uptime:")->grid(-row=>2, -column=>0);
 $tl->Label(-textvariable=>\$object->{'uptime'})->grid(-row=>2,
-column=>1);
 $tl->Button(-text=>"Exit",
-command=>sub{$tl->destroy()})->grid(-row=>20, -column=>0); # Create an
exit button.
 $tl->repeat(5000, sub{$object->update_status()}); # Update the status
every 5 seconds.
}
---------

I simply create a window with two buttons. One is an exit button, and
one calls new_object() when it is clicked. 
new_object() calls new() on My::Class, which returns the newly created
object.

Here's the bit I couldn't quite understand. My::Class::DESTROY() is not
called when the 'Exit' button on $tl is clicked. If I click on 'Create
an object' several times, and close any or all of the windows it
creates, there is no message printed when the window is closed. Only
when I click on 'Exit' in the main window, thereby terminating the whole
program, do I ever see DESTROY() called. And then it's once for every
object created while the program was running, irrespective of whether
it's window has been closed or not.  Try it, the above example is
complete.

This may be all irrelevant, however, as it looks like you've already
given me the answer I was after. This is, to me anyway, strange
behaviour from Tk, but I should be able to avoid it by keeping an array
and weakening the references in it. 

Regards,
             Leigh
 
Leigh Sharpe
Network Systems Engineer
Pacific Wireless
Ph +61 3 9584 8966
Mob 0408 009 502
Helpdesk 1300 300 616
email lsharpe at pacificwireless.com.au
web www.pacificwireless.com.au





-----Original Message-----
From: Jacinta Richardson [mailto:jarich at perltraining.com.au] 
Sent: Tuesday, 13 January 2009 12:23 PM
To: Leigh Sharpe
Cc: melbourne-pm at pm.org
Subject: Re: [Melbourne-pm] TK repeats, scoping and keeping track of
objects

G'day Leigh,

Leigh Sharpe wrote:

> sub new_object
> {
>  my $object=My::Class->new();           # Create a new object to
monitor
>  my $tl=$mw->Toplevel;                      # Create a top-level
window.
>  $tl->title($object->{'name'});                # Set the title of the
> new window.
>  $tl->Label(-text=>"Name:")->grid(-row=>0, -column=>0); # Add Labels 
> to window.
>  $tl->Label(-textvariable=>\$object->{'name'})->grid(-row=>0, 
> -column=>1);  $tl->Label(-text=>"IP Address")->grid(-row=>1, 
> -column=>0);  
> $tl->Label(-textvariable=>\$object->{'ip'})->grid(-row=>1, 
> -column=>1);  $tl->Label(-text=>"Uptime:")->grid(-row=>2, -column=>0);

> $tl->Label(-textvariable=>\$object->{'uptime'})->grid(-row=>2, 
> -column=>1);  $tl->Button(-text=>"Exit", 
> -command=>sub{$tl->destroy()})->grid(-row=>20, -column=>0); # Create
an exit button.
>  $tl->repeat(5000, sub{$object->update_status()}); # Update the status

> every 5 seconds.
> }

> Now this works as it is, but I just know it won't scale well. I would 
> rather have a single class method I could call which would run
> update_status() on all objects of it's class, and call it from the 
> main window ($mw in this case). So my first question is, how can a 
> class access all instances of itself? I considered creating an array 
> in which I could store a reference to all objects, but that just seems

> wrong, 'cause it will prevent objects from going out of scope when all

> other references to them are deleted.

You can create an array of all the objects and then weaken those
references so that the object will still be cleaned up upon going out of
scope.  You then want to make sure that you handle holes in your array.
For example:

	# Weak.pm
	package Weak;
	use strict;
	use Scalar::Util qw(weaken);

	my @all_objects;

	sub new {
	        my ($class, $name) = @_;

	        my $self = { name => $name };
	        $self = bless $self, $class;

	        # Add reference to object to array
	        push @all_objects, $self;

	        # Weaken reference so that it doesn't count for garbage
	        # collection
	        weaken $all_objects[-1];

	        return $self;
	}

	sub update_all {
	        my ($class, @args) = @_;

	        foreach my $self (@all_objects) {
			# Skip if it's empty or looks funny
	                next unless ($self and ref $self eq $class);

	                print $self->{name}, "\n";
	        }
	}

	1;

	# Test script
	use strict;
	use Weak;

	my $object1 = Weak->new("obj1");
	my $object2 = Weak->new("obj2");
	{
		# Object 3 is only in this scope
	        my $object3 = Weak->new("obj3");

		# Should print out 1 - 3.
	        Weak->update_all();
	}
	# One last object
	my $object4 = Weak->new("obj4");

	print "\n\n";
	Weak->update_all();


	__END__
	obj1
	obj2
	obj3


	obj1
	obj2
	obj4


Still, I can't but think that you might be approaching the problem the
wrong way.  I'd need to know more about what you're doing in order to
suggest a better answer.

> The second question is in relation to keeping $object in scope. If I 
> remove the last line of the above function, $object goes out of scope 
> as soon as the function completes. Having a label with 
> -textvariable=>\$object{'anything'} isn't sufficient to keep $object 
> in scope.

Is there a reason you're not returning your object?  It seems odd to
worry about whether objects can properly be cleaned up when going out of
scope (as you mentioned above) and then not actually pass them back into
the program so that you can use them in the scope for which you've
created them.  I haven't done a lot of TK programming, so maybe that's
where I'm missing this, but usually one creates an object, and passes it
out to the code that wanted to use it, and then when it goes out of
scope naturally it's cleaned up....

> So, if I was to create a class method as required, there would be no 
> objects to call update_status() on.
> However, with the function as it is above, even destroying $tl (when 
> the user clicks on the 'Exit' button) doesn't call DESTROY on $object.

> There must be some reference to the object somewhere, created by 
> $tl->repeat(), which isn't going out of scope when $tl is destroy()ed.

This doesn't seem right.  In your new_object sub you create $object and
it can be accessed by that name.  After that sub has finished $object
can still be accessed by $tl's argument to repeat.  Once $tl has been
cleaned up, unless it has stored that subroutine some other way, the
anonymous subroutine cannot be accessed so it should be cleaned up and
then $object cannot be accessed so it should be cleaned up.  Add a
DESTROY method to your class and see what's going on, because I'd expect
it to be cleaned up.  For example:

	sub DESTROY {
	        my ($self) = @_;
	        print $self->{name}, " is being destroyed\n";
	}

All the best,

	J

-- 
   ("`-''-/").___..--''"`-._          |  Jacinta Richardson         |
    `6_ 6  )   `-.  (     ).`-.__.`)  |  Perl Training Australia    |
    (_Y_.)'  ._   )  `._ `. ``-..-'   |      +61 3 9354 6001        |
  _..`--'_..-_/  /--'_.' ,'           | contact at perltraining.com.au |
 (il),-''  (li),'  ((!.-'             |   www.perltraining.com.au   |


More information about the Melbourne-pm mailing list