puff has asked for the wisdom of the Perl Monks concerning the following question:

To make a long and ugly story short if
Win32::OLE->MessageLoop
Is executing perl’s alarm function does not work. I’ve tested it very thoroughly and am VERY sure that alarm does not trigger it’s event routine while MessageLoop has control. While it is, at least for my purposes, unfortunate, I’m not sure it is a bug.

I have a technique that uses Win32::OLE->SpinMessageLoop that works to a point. Unfortunately, it changes the timing of the OLE object’s events. Since for my purposes I care about the timing, it is not a trackable solution.

That said, does anyone know of some sort of work around? Perhaps a technique to timeout the Win32::OLE->MessageLoop routine? Perhaps a way to cause timed execution of a perl event routine on a timer that is consistent with Win32::OLE->MessageLoop (say an OLE timer)?

Replies are listed 'Best First'.
Re: Win32::OLE and timeout
by InfiniteSilence (Curate) on Nov 04, 2005 at 23:54 UTC
    puff, is there a way you can post some more of your code? I was trying to recreate your problem using the following:

    #!/usr/bin/perl -w use strict; use Win32::OLE qw|EVENTS|; my $excel = Win32::OLE->CreateObject("Excel.Application",'Quit'); Win32::OLE->WithEvents($excel,\&Event); $excel->{Visible} = 1; #...etc... Win32::OLE->MessageLoop(); sub Event { my ($obj, $event,@params) = @_; print "Event triggered: '$event'\n"; } 1;
    But to no avail, the above gives me nothing but errors:

    perl colorcellexcel.pl Win32::OLE(0.1702) error 0x80004002: "No such interface supported" at +colorcellexcel.pl line 7 eval {...} called at colorcellexcel.pl line 7

    Celebrate Intellectual Diversity

Re: Win32::OLE and timeout
by puff (Beadle) on Nov 05, 2005 at 00:35 UTC
    Here is a bit of perl that demonstrates the underlying problem with alarm.
    #!/usr/bin/perl -w use 5.006; use strict; use warnings; use Win32::OLE qw(EVENTS); use Time::HiRes qw(gettimeofday); Win32::OLE->Option(_Unique => 1); sub IENavigate( $ ); $| = 1; # do not buffer output my $IE; my $IETimeout = 60; my @urls = qw( http://www.google.com http://www.cnn.com/ ); # start ie, connect to events, make IE visible, connect alarm handler $IE = Win32::OLE->new("InternetExplorer.Application") || die "Could not start Internet Explorer.Application\n"; Win32::OLE->WithEvents($IE,\&IEEvent,"DWebBrowserEvents2"); $IE->{visible} = 1; $SIG{ALRM} = \&IEAlarm; # visit some URLs foreach (@urls){ print "Navigate to $_\n"; my $seconds = IENavigate( $_ ); print "took $seconds\n"; } # we're done print "Done\n"; # kill this IE $IE->Quit(); exit; # ################################################################### # sub IENavigate( $ ) { my $url = shift; my $seconds = 0; print "IENavigate $url\n"; alarm $IETimeout; $IE->navigate($url); Win32::OLE->MessageLoop(); print "Finished Blocking\n"; return; } sub IEEvent(){ my ($Obj,$Event,@Args) = @_; print "IEEvent '$Event'\n"; } sub IEAlarm(){ my $sig = shift; print "IEAlarm $sig\n"; Win32::OLE->QuitMessageLoop(); }
      I had a lot of weird ideas about this code like using fork, but after looking at it for a while I settled on simply changing the following:

      ... my $IE; my $IETimeout = 60; my $timeAtStart; #new global var ... sub IENavigate( $ ) { my $url = shift; my $seconds = 0; $timeAtStart = time unless ($timeAtStart); print "IENavigate $url\n"; alarm $IETimeout; $IE->navigate($url); Win32::OLE->MessageLoop(); print "Finished Blocking\n"; return; } sub IEEvent(){ my ($Obj,$Event,@Args) = @_; if (($IETimeout) && ($timeAtStart) && (time > ($timeAtStart + $IET +imeout) )) {&IEAlarm(17)}; print "IEEvent '$Event' @ " . time . qq|\n|; }

      Which produces, not surprisingly...

      IEEvent 'StatusTextChange' @ 1131174480 IEEvent 'CommandStateChange' @ 1131174480 IEEvent 'StatusTextChange' @ 1131174480 IEEvent 'StatusTextChange' @ 1131174480 IEEvent 'CommandStateChange' @ 1131174480 IEEvent 'StatusTextChange' @ 1131174480 ... IEEvent 'CommandStateChange' @ 1131174520 IEEvent 'CommandStateChange' @ 1131174520 IEAlarm 17 IEEvent 'CommandStateChange' @ 1131174521 Finished Blocking
      There are some strange things in this code like:
      Use of uninitialized value in concatenation (.) or string at foo.pl li +ne 37. ... #which is ==36== my $seconds = IENavigate( $_ ); ==37== print "took $seconds\n";
      when IENavigate doesn't return anything...? At any rate, if you are looking to see how long it takes to load pages, etc. wouldn't you be better served with LWP::Simple and Benchmark or something?

      Celebrate Intellectual Diversity

        I’ve been tied up for a bit and did not see this reply until today.

        The approach is interesting (hadn’t thought of this attack) but unfortunately I do not think that it will work reliably. The problem is that it assumes that events will continue to occur during the interval. While they frequently do, they do NOT ALWAYS DO so. The consequence is that you can not rely on the event routine being executed. The only thing I can find after looking at the Win32::OLE code that is guaranteed to be true is that you are in a classic and very tight Com message dispatch loop.

        Does anyone know how to start a conversation with the Win32::OLE folks? I’ve tried cpan and doing a bug report. Also sent an email to the listed author address.
        BTW, I tested the proposed code and it does NOT work unless the underlying page takes more than Timeout to load and still generates events. If you test it you MUST NOT move the mouse about the IE window as that generates events that would not otherwise occur and can thus distort the results.
        #!/usr/bin/perl -w use 5.006; use strict; use warnings; use Win32::OLE qw(EVENTS); use Time::HiRes qw(gettimeofday); Win32::OLE->Option(_Unique => 1); sub IENavigate( $ ); $| = 1; # do not buffer output my $IE; my $IETimeout = 60; my $timeAtStart; #new global var my @urls = qw( http://www.google.com http://www.cnn.com/ ); # start ie, connect to events, make IE visible, connect alarm handler $IE = Win32::OLE->new("InternetExplorer.Application") || die "Could not start Internet Explorer.Application\n"; Win32::OLE->WithEvents($IE,\&IEEvent,"DWebBrowserEvents2"); $IE->{visible} = 1; $SIG{ALRM} = \&IEAlarm; # visit some URLs foreach (@urls){ print "Navigate to $_\n"; my $seconds = IENavigate( $_ ); print "took $seconds\n"; } # we're done print "Done\n"; # kill this IE $IE->Quit(); exit; # ################################################################### # sub IENavigate( $ ) { my $url = shift; my $seconds = 0; print "IENavigate $url\n"; $timeAtStart = time unless ($timeAtStart); alarm $IETimeout; $IE->navigate($url); Win32::OLE->MessageLoop(); print "Finished Blocking\n"; return; } sub IEEvent(){ my ($Obj,$Event,@Args) = @_; if (($IETimeout) && ($timeAtStart) && (time > ($timeAtStart + $IET +imeout) )) {&IEAlarm(17)}; print "IEEvent '$Event'\n"; } sub IEAlarm(){ my $sig = shift; print "IEAlarm $sig\n"; Win32::OLE->QuitMessageLoop(); }