Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 31.01.2007 20:05:18 von Andy
I'm using the CPAN Tk::Scheduler module to time control the execution
of subroutines within my Perl script. I want to display the results
of a scheduled task in a tk text widget. But, when my subroutine gets
invoked by the scheduler via the -command switch, the called
subroutine itself seems to be unable to talk to any Perl::Tk widgets;
all such calls within the subroutine hang. Everything else seems to
work ok.
Does anyone know why? Do I have to call some sort of "refresh" tk
command or release tk resources?
use Tk;
use Tk::Schedule;
my $mw = MainWindow->new;
my $s = $mw->Schedule(
-interval => 60,
-repeat => "once",
-command => [\&run, "junk"],
-comment => "1-9999"
)->pack();
my $txtbox=$mw->Text(-width => 79,
-height => 8,
)->pack();
$txtbox->insert(end, "this works\n");
MainLoop;
sub run{
print "start run\n";
$txtbox->insert(end,"this just hangs\n");
print "exit run\n";
}
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 31.01.2007 21:14:05 von Andy
Some further investigation shows that the tk::scheduler executes the -
command switch by executing the assigned code throgh a Perl eval()
statement.
I suspect what is happening is that the Perl code inside the eval() is
out of scope to variables outside the eval statement (including the
scheduler.pm module, and the parent Perl program utilizing the
scheduler).
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 01.02.2007 15:01:12 von ac
"Andy" wrote in message
news:1170274445.027656.29230@k78g2000cwa.googlegroups.com...
> Some further investigation shows that the tk::scheduler executes the -
> command switch by executing the assigned code throgh a Perl eval()
> statement.
>
> I suspect what is happening is that the Perl code inside the eval() is
> out of scope to variables outside the eval statement (including the
> scheduler.pm module, and the parent Perl program utilizing the
> scheduler).
>
Thank you for following up on your own posting with some insight. About 50%
of postings are useless when googling because the author never followed up
with an answer.
Allan
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 01.02.2007 17:05:57 von Andy
I wish I could get it working...
The problem seems to have something to do with Perl references
(covered by the Perlref doc that comes with ActiveState). The
documentation says that in terms of capabilities, all memory within a
Perl program is referencable by anything (both perl modules and
calling programs have equal and full access to each other) - you just
have to ask for it.
Unlike other languages, Perl seems to have hundreds of ways to
reference memory for variables, arrays, and code. The differences
revolve around scope and encapsulation of whatever is being
referenced.
What is unusual about Perl is that you have a choice of whether or not
to make your reference use a symbolic table. Its when a "local"
symbolic table is used for a reference that accessibility problems
occur.
Perl borrows a concept of "closure" from the language LISP that seems
to allow you to dynamically define what "scope" really means for any
particular reference. I found the documentation very confusing.
"Closure" seems to be stipulated by a combination of reference
operators (such as \&, @, $), the context that they appear in (such as
inside eval(), do{}, or sub{}), and Perl directives (such as EXPORT).
And, several of these constructs even have a few variations (such as
@EXPORT_OK and @EXPORT).
I have found it isn't just TK widgets that I can't reference. Any
subroutines that I call from inside of the run() subroutine that are
part of the same program run() resides in can't be called either -
they also hang.
Any help in understanding this would be much appreciated.
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 01.02.2007 22:28:12 von Andy
Although this doesn't fix the Tk access problem, I found that if you
give your Perl program an arbitrary and unique package name, such as
placing the following line at the top of your listing:
package Tk::BulkE;
that this package name will automatically be included as the first
line in the subroutine specified by the -command switch inside the
code that gets evaluated:
sub run{
print "start run\n";
$txtbox->insert(end,"this just hangs\n");
print "exit run\n";
}
becomes
{
package Tk::BulkE;
print "start run\n";
$txtbox->insert(end,"this just hangs\n");
print "exit run\n";
}
The eval statement inside of the scheduler then runs the code in the
context of your original Perl program, allowing full access to all of
your subroutines and variables.
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 02.02.2007 10:46:02 von dominique.dumont
"Andy" writes:
> Does anyone know why? Do I have to call some sort of "refresh" tk
> command or release tk resources?
Have you tried with an anonymous sub ? This sub is stored inside a
lexical variable so there's no problem with namespace.
I.e. (untested) :
use Tk;
use Tk::Schedule;
my $mw = MainWindow->new;
my $txtbox ; # variable declared before anon sub creation
my $run = sub {
print "start run\n";
$txtbox->insert(end,"this just hangs\n");
print "exit run\n";
} ;
my $s = $mw->Schedule(
-interval => 60,
-repeat => "once",
-command => [ $run, "junk"],
-comment => "1-9999"
)->pack();
$txtbox=$mw->Text(-width => 79,
-height => 8,
)->pack();
$txtbox->insert(end, "this works\n");
MainLoop;
# HTH
--
Dominique Dumont
"Delivering successful solutions requires giving people what they
need, not what they want." Kurt Bittner
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 02.02.2007 16:20:12 von Andy
I've read your reply and tried moving the run subroutine into a
variable, and declaring it and the tk read only text widget before the
creation of the scheduler. The code compiles and runs. But, when the
scheduler tries to parse the code in the command variable, the
following error occurs (buried deep inside of some perl modules the
scheduler itself uses):
Tk::Error: Usage: ->coderef2text(CODEREF) at C:/Perl/site/lib/Tk/
Schedule.pm line 374
Carp::croak at C:/Perl/lib/Carp.pm line 269
B::Deparse::coderef2text at C:/Perl/lib/B/Deparse.pm line 671
Tk::Schedule::AddTime at C:/Perl/site/lib/Tk/Schedule.pm line 374
Tk callback for .schedule.frame.button
Tk::__ANON__ at C:/Perl/site/lib/Tk.pm line 252
Tk::Button::butUp at C:/Perl/site/lib/Tk/Button.pm line 111
(command bound to event)
As I haven't written the scheduler or the other modules it uses, I
have no idea what this error is saying went wrong. But, I suspect it
has to do with something with the command code being treated as plain
text before it is executed in the eval statement within the
shceduler. If so, we seem to be losing the memory references to
variables again.
But, thanks for the suggestion.
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 02.02.2007 16:26:38 von Andy
oops I found a typo - got rid of the Tk::Error: Usage: -
>coderef2text(CODEREF) at C:/Perl/site/lib/Tk/
Schedule.pm line 374 error. But, calls to the widgets still hang.
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 02.02.2007 16:37:28 von Andy
But, if I keep the creation of the read only text widget before the
creation of the scheduler as Dominique Dumont had suggested, and pass
the variable that holds the reference to the read only text widget to
the command as an argument to the run subroutine, and I keep the
package name for the main Perl program, the run subroutine is then
able to talk to the tk widget!
package Tk::BulkE;
use Tk;
use Tk::Schedule;
my $mw = MainWindow->new;
my $txtbox=$mw->Text(-width => 79,
-height => 8,
)->pack();
my $s = $mw->Schedule(
-interval => 60,
-repeat => "once",
-command => [\&run, $txtbox],
-comment => "1-9999"
)->pack();
$txtbox->insert(end, "this works\n");
MainLoop;
sub run{
my $txtbox=shift;
print "start run\n";
$txtbox->insert(end,"and this now works (previously it hanged)
\n");
print "exit run\n";
}
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 05.02.2007 11:46:58 von dominique.dumont
"Andy" writes:
> I've read your reply and tried moving the run subroutine into a
> variable, and declaring it and the tk read only text widget before the
> creation of the scheduler. The code compiles and runs. But, when the
> scheduler tries to parse the code in the command variable, the
> following error occurs (buried deep inside of some perl modules the
> scheduler itself uses):
>
> Tk::Error: Usage: ->coderef2text(CODEREF) at C:/Perl/site/lib/Tk/
> Schedule.pm line 374
> Carp::croak at C:/Perl/lib/Carp.pm line 269
> B::Deparse::coderef2text at C:/Perl/lib/B/Deparse.pm line 671
> Tk::Schedule::AddTime at C:/Perl/site/lib/Tk/Schedule.pm line 374
Looks like Schedule use deparse and then (probably) eval. In this
case, closure will not work as the lexical variables defined in your
module are not in the scope of the eval run by Schedule.
> Tk callback for .schedule.frame.button
> Tk::__ANON__ at C:/Perl/site/lib/Tk.pm line 252
> Tk::Button::butUp at C:/Perl/site/lib/Tk/Button.pm line 111
>
> (command bound to event)
>
> As I haven't written the scheduler or the other modules it uses, I
> have no idea what this error is saying went wrong. But, I suspect it
> has to do with something with the command code being treated as plain
> text before it is executed in the eval statement within the
> shceduler. If so, we seem to be losing the memory references to
> variables again.
Yes, because lexical variable cannot get through a deparse/eval. OTOH,
regular variables can.
> But, thanks for the suggestion.
You're welcome.
Cheers
--
Dominique Dumont
"Delivering successful solutions requires giving people what they
need, not what they want." Kurt Bittner
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 05.02.2007 11:46:59 von dominique.dumont
"Andy" writes:
> But, if I keep the creation of the read only text widget before the
> creation of the scheduler as Dominique Dumont had suggested, and pass
> the variable that holds the reference to the read only text widget to
> the command as an argument to the run subroutine, and I keep the
> package name for the main Perl program, the run subroutine is then
> able to talk to the tk widget!
>
> package Tk::BulkE;
>
> use Tk;
> use Tk::Schedule;
>
> my $mw = MainWindow->new;
>
> my $txtbox=$mw->Text(-width => 79,
> -height => 8,
> )->pack();
>
> my $s = $mw->Schedule(
> -interval => 60,
> -repeat => "once",
> -command => [\&run, $txtbox],
The problem was related to $txtbox closure. Here, the $txtbox ref is
copied when you call Schedule, so you don't rely on a lexical.
HTH
--
Dominique Dumont
"Delivering successful solutions requires giving people what they
need, not what they want." Kurt Bittner
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 05.02.2007 20:32:43 von Andy
Thanks Dominique!
I'ld like to recap/summarize this discussion thread to make it easier
for those reading it in the future.
Closure is the Perl equivalent of namespaces and encapsulation of
variables and code. Since Perl programs are executed by a central
interpreter, there is no O/S imposed isolation of scope between Perl
programs and the modules they call (they all run under the same
process the interpreter is on).
As long as you pass a reference (a memory address) for a variable or a
subroutine, that variable can be read/written or that subroutine can
be executed from anywhere, regardless of where it was first declared.
Reference passing through switches (such as -command) is a common way
for Perl/Tk programs and widgets to talk to each other.
To pass a reference for a subroutine declared in a Perl program to a
widget, prefix the subroutine's name with \&
To pass a reference for a variable declared in a Perl program to a
widget, prefix the variable's name with \$
To execute an externally referenced subroutine from inside a Perl
module, precede the variable that received the reference with &$ and
treat the entire thing as a normal subroutine call.
To work with the value in an externally referenced variable from
inside a Perl module, precede the variable that received the reference
with $$ and treat the entire thing as a normal variable.
ie, given myvar and mysub reside in your perl program
$myvar="hello world";
mysub{
($greeting) = @_;
print $$greeting;
}
you can have a perl module (or widget) directly access these items
without making any copies of anything by first passing the references:
-command => [\&mysub,\$myvar]
and having the receiving perl module (or widget) use the references to
execute the external code in some way:
modsub=shift; #captures the ref \&mysub
modvar=shift; #captures the ref \$myvar
&$modsub($$modvar); #call the external mysub method and pass it hello
world
The &$ and $$ are called dereferencing operators, and they can also be
used anywhere to "execute" a reference wherever it appears.
There are, however, some Perl Modules that execute a copy of
referenced code (by value) rather than doing it directly (by
reference).
These use another CPAN module called B::Deparse that is essentially a
Perl de-compiler. Given a reference, Deparse can expand the tokens it
finds there into Perl code using its coderef2text function:
use B::Deparse;
$deparse= B::Deparse->new();
modsub=shift; #captures the ref \&mysub
$modcode=$deparse->coderef2text(modsub); #modcode now contains mysub
listing
The Perl module then re-compiles the de-compiled code using eval() to
execute it:
eval($modcode);
This is useful when your Perl program needs memory to be persisted in-
between separate invocations of the Perl interpreter. Rather than
persisting addresses (which can change upon reloading), it's easier to
recreate whatever needs persistance and to assign it a new memory
address.
The tk::scheduler does this because it persists scheduled tasks, even
when it is shut down and restarted again at a later date. If the
scheduler stored the addresses to scheduled code, those addresses
could be invalid the next time it was reloaded back into the
interpreter. Instead, it stores a copy of the subroutine to be
executed, which is why I encountered all the previous access problems
to subroutines other than my main one - they weren't included in the
deparser decompile.
The deparser, however, is smart enough to include any package names
for the listing the decompiled code originally appeared in.
The package name acts as a namespace that identifies a particular
symbolic table that may exist in the Perl Interpreter. When the
eval() statement has a package name, it can access the symbolic table
to resolve any other references within the decompiled code that point
to code currently loaded in the Perl interpreter. In a way, execute
by value code can execute code by reference by using labels instead of
numeric memory addresses.
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 06.02.2007 09:47:15 von dominique.dumont
"Andy" writes:
> Thanks Dominique!
You're welcome.
> ie, given myvar and mysub reside in your perl program
>
> $myvar="hello world";
>
> mysub{
> ($greeting) = @_;
Beware, here, $greeting is a global variable. You should use a
lexical variable:
my ($greeting) = @_ ;
> print $$greeting;
> }
[snip]
> you can have a perl module (or widget) directly access these items
> without making any copies of anything by first passing the references:
>
> -command => [\&mysub,\$myvar]
>
> and having the receiving perl module (or widget) use the references to
> execute the external code in some way:
>
> modsub=shift; #captures the ref \&mysub
> modvar=shift; #captures the ref \$myvar
You forgot the $ (and probably the lexical declaration):
my $modsub=shift; #captures the ref \&mysub
my $modvar=shift; #captures the ref \$myvar
> &$modsub($$modvar); #call the external mysub method and pass it hello
> world
This can also be run with this syntax:
$modsub -> ($$modvar) ;
> The &$ and $$ are called dereferencing operators, and they can also be
> used anywhere to "execute" a reference wherever it appears.
>
>
>
> There are, however, some Perl Modules that execute a copy of
> referenced code (by value) rather than doing it directly (by
> reference).
>
> These use another CPAN module called B::Deparse that is essentially a
> Perl de-compiler. Given a reference, Deparse can expand the tokens it
> finds there into Perl code using its coderef2text function:
>
> use B::Deparse;
> $deparse= B::Deparse->new();
> modsub=shift; #captures the ref \&mysub
> $modcode=$deparse->coderef2text(modsub); #modcode now contains mysub
You forgot the $ (and probably the lexical declaration):
use B::Deparse;
my $deparse = B::Deparse->new();
my $modsub = shift; #captures the ref \&mysub
# modcode now contains mysub listing
my $modcode = $deparse->coderef2text($modsub);
> The tk::scheduler does this because it persists scheduled tasks, even
> when it is shut down and restarted again at a later date. If the
> scheduler stored the addresses to scheduled code, those addresses
> could be invalid the next time it was reloaded back into the
> interpreter. Instead, it stores a copy of the subroutine to be
> executed, which is why I encountered all the previous access problems
> to subroutines other than my main one - they weren't included in the
> deparser decompile.
I hope there's a big warning in Tk::Scheduler doc. If not, I think you
should send a patch to its author.
Cheers
--
Dominique Dumont
"Delivering successful solutions requires giving people what they
need, not what they want." Kurt Bittner
Re: Perl Tk:Scheduler command called subroutine fails to talk to tk widgets
am 06.02.2007 17:08:12 von Andy
How a Perl Module is implemented determines what referencing
techniques you have to use to make it work. Here's another example
that might help those in the future that mixes radiobutton widgets,
text widgets, and the tk::scheduler.
Unlike text widgets, radiobutton state cannot be read directly.
Instead, radiobuttons dynamically set (by reference) a shared
variable. In this example, this variable has to also be accessed by
the scheduler which does everything by value. I've created my own
radiobutton state variable called $environment. The text box address
is stored in $results.
package Tk::BulkE;
use Tk;
use Tk::ROText;
use Tk::Schedule;
my $environment=0;
#CREATE AN APPLICATION WINDOW (SEE PERL/TK LAYOUT MANAGERS)
my $mw = MainWindow->new;
my $frame8 = $mw->Frame; # scheduler
$frame8->pack(-fill => "x");
my $frame4 = $mw->Frame;
$frame4->pack(-fill => "x",
-pady => 5);
my $frame5 = $mw->Frame;
$frame5->pack(-fill => "x",
-pady => 5);
#THIS IS A TEXTBOX TO DISPLAY MESSAGES IN
my $results=$frame4->Text(-width => 79,
-height => 8,
-background => "#7800e400ff00"
)->pack();
#TK::RADIOBUTTONS TO SELECT A STATE (BY REF CALLING)
my $optDev=$frame5->Radiobutton(-text => "Development",
-value => "dev", # NOT REALLY USING THIS
-command => sub{
package Tk::BulkE; #DEPARSE NOT USED HERE, SO ADD THE PACKAGE
NAME
$results->configure(-background=> "#7800e400ff00");
$results->insert(end, "WARNING: Email will be sent internally
\n");
$results->see(end);
setEnvironment(0);
}
)->pack(-side => "left");
my $optProd=$frame5->Radiobutton(-text => "Production",
-variable => \$mode,
-value => "prod", # NOT REALLY USING THIS
-command => sub{
package Tk::BulkE; #DEPARSE NOT USED HERE, SO ADD THE PACKAGE
NAME
$results->configure(-background=> "#ff00d9000000");
$results->insert(end, "CAUTION: Email will be sent to outside
\n");
$results->see(end);
setEnvironment(1);
}
)->pack(-side => "left");
#PASS REFERENCES TO THE SCHEDULER (BY VALUE CALLING)
my $ctrl={};
$ctrl{results}=$results; #ADDRESS FOR TEXT WIDGET
$ctrl{mode}=\$environment; #REFERENCE TO RADIOBUTTON STATE VARIABLE
my $s = $frame8->Schedule(
-interval => 60,
-repeat => "once",
-command => [\&run, #PASSED BY REFERENCE
$ctrl #BY REFERENCE ADDRESSES PASSED BY VALUE
],
-comment => "1-9999"
)->pack(-fill => "x",
-padx => 25);
MainLoop;
#RADIOBUTTON COMMAND SWITCH CALLS THIS
sub setEnvironment{
$environment=shift;
anothersub($results);
}
#SCHEDULER COMMAND SWITCH CALLS THIS
sub run{
#DEPARSE AUTOMATICALLY ADDS package Tk::BulkE; HERE
my $ctrl=shift; #get the references
$ctrl{results}->insert(end, "Task started\n"); #DISPLAY MESSAGE IN
TEXT WIDGET
my $mode=$ctrl{mode}; #CAPTURE THE ADDRESS FOR RADIOBUTTON STATE
VARIABLE
#ACT ON RADIOBUTTON STATE
if($$mode>0){
$ctrl{results}->insert(end, "Production run requested\n");
anothersub($ctrl{results});
}else{
$ctrl{results}->insert(end, "Development run requested\n");
}
}
#BOTH THE RADIOBUTTON AND SCHEDULER COMMAND SWITCHES CALL THIS
sub anothersub{
(my $results)= @_;
$results->insert(end, "this works too!\n");
}
That's it for this story - see'ya everyone!