Go Premium for a chance to win a PS4. Enter to Win

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 270
  • Last Modified:

call a modules INIT

This is a spinoff of thread:

The Question:

how can i call a modules INIT-section explicitly from another module or main or eval()?

example:

eval
qq{
      use Win32::API;
      Win32::API->Import( "winmm.dll", "long timeGetTime()" );
};

throws a warning because Win32::API::INIT is not executed within an eval-statement.

so how can i make INIT run without having to change the Win32::API´s code?



0
holli
Asked:
holli
  • 5
  • 3
  • 2
1 Solution
 
holliAuthor Commented:
0
 
terageekCommented:
I don't know if this will work, and it isn't too direct or pretty, but here is a shot...

#Search @INC for Win32::API
foreach my $path (@INC) {
    if (-e $path/Win32/API.pm) {
      # Open the .pm file and grab the INIT block
        my $init_block = "";
        open PM "<$path/Win32/API.pm");
        while ($line = <PM>) {
           #Start grabbing the INIT block
            if ($line =~ /INIT/) {
                $init_block = $line;
            } elsif ($init_block ne "") {
                $init_block .= $line;
            }
            if ($init_block =~ /{/) {
               #Count the number of "{" and "}"
                my $open = scalar @{[$init_block =~ /\{/g]};
                my $closed = scalar @{[$init_block =~ /\}/g]};
               #Eval the block if the block is closed
                if ($open == $closed) {
                  # Get rid of "INIT" and make sure this
                  # is evaluated in the right package context
                    $init_block =~ s/^.*?INIT/package Win32::API;/;
                    eval $init_block;
                  # Might need to set package back here???
                    $init_block = "";
                }
            }
        }
        last;
    }
}

The above code makes some assumptions about proper formatting of the pm code.  You may also need to put yourself back into your own package.
0
 
terageekCommented:
Change

if ($line =~ /INIT/) {

to

# Parse out comments
$line =~ s/(^|[^\$])#.*/$1/;
if ($line =~ /\bINIT\b/) {


That should skip cases where "INIT" is in a comment, and it will avoid issues with other words containing "INIT", like "$INITIALVALUE".
0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
holliAuthor Commented:
i did a little investigation.

package t;

sub INIT
{
     print "INIT";
}

sub import
{
   my ($pkg, @args) = @_;
   print "package $pkg loaded, args ", join(':',@args), "\n";
   @foo::args = @args;
 
   my $init = $t::{INIT};
   my $foo  = $t::{foo};
   $init = \&$init;
   $foo = \&$foo;
   print "* $init *";
   print "* $foo *";
   &foo();
   &$init();
   print "end";
}

sub foo
{
     print "FOO";    
}

1;

______________


#t.pl
use lib qw (c:/ );
use t qw (x y);
print "main";


outcome:
package t loaded, args x:y
Undefined subroutine &t::INIT called at c://t.pm line 21.
BEGIN failed--compilation aborted at c:\t.pl line 2.
* CODE(0x186a230) ** CODE(0x186a170) *FOO


Why?
"CODE(0x186a230)" looks like a code-reference. why can´t i call it?
0
 
boazgOLDCommented:
the main problem with this specific itin, it seems, is that id reads from the <DATA> stream. before you eval the code,(1) read until __DATA__, and (2) replace <DATA> with <PM>, or whatever you want to call the file handle. another few bugs needed fixing... use q(), not qq{} (both curley brackets and qq caused problems). there was a spot in the INIT that used # not in comment context... had to fix that

also, the init in this case is in API/type.pm not in API.pm

result (based on code above)
      foreach $path (@INC) {
    if (-e "$path/Win32/API/type.pm") {
      # Open the .pm file and grab the INIT block
        my $init_block = "";
        open PM, "<$path/Win32/API/type.pm";
        while ($line = <PM>) {
           #Start grabbing the INIT block
               if ($line !~ /\/[^\/]*#[^\/]*\//) {
               $line =~ s/(^|[^\$])#.*/$1/;}
           if ($line =~ /\bINIT\b/) {
                $init_block = $line;
            } elsif ($init_block ne "") {
                $init_block .= $line;
            }
            if ($init_block =~ /{/) {
               #Count the number of "{" and "}"
                my $open = scalar @{[$init_block =~ /\{/g]};
                my $closed = scalar @{[$init_block =~ /\}/g]};
               #Eval the block if the block is closed
                if ($open == $closed) {
                  # Get rid of "INIT" and make sure this
                  # is evaluated in the right package context
                    $init_block =~ s/^.*?INIT/package Win32::API::Type;/;
                     #set correct file handle
               *Win32::API::Type::DATA=*main::PM;
                      #read until _DATA_
                                $readline=<PM>;
                      while($readline !~ /__DATA__\n/){$readline=<PM>;};
                       ######back to old code#####
                    eval $init_block;
                              print $@."\n";
                  # Might need to set package back here???
                    $init_block = "";
                              last;
                }
            }
        }
    }
}


hope that helps
0
 
holliAuthor Commented:
that is ugly.

what do think about this? change:

INIT { #old code

to

INIT { &SINIT; }

sub SINIT { #old code

then you can call Module::SINIT();

quite elegant, isn´t it. or are there hidden penalties.
i am currently working on a module that automates that step by using a code-filter (Filter::Simple).
0
 
holliAuthor Commented:
i have this now:

Module Eval::INIT:

package Eval::INIT;

my $filter;

use Filter::Simple sub
{
      return if $filter++;
      
      my $i=0;
      while ( $_ =~ s/\bINIT(?!\x00)([\s\n]+)/\1INIT\x00 { \&SINIT$i; } \nsub SINIT$i\1/ms ) {$i++;};       
      s/\x00//msg;
};

1;

Module t.pm

package t;

use Eval::INIT;

INIT
{
      print "INIT\n";
}

1;

and t.pl

eval "use t; &t::SINIT0();";
print "main";

output:
INIT
main

what do you think?

0
 
boazgOLDCommented:
beutiful! and it works for your problem... you do have to change the module code, but only by adding one line...

well, it would seem you solved you own problem!
0
 
holliAuthor Commented:
it would be nice if coud "inject" the use Evaled::Filter into the module, like

eval "use Evaled::InjectFilter qw (Win32::API::Type)";

from main::.

do you think this could make it to cpan?
what to do with the points?
0
 
boazgOLDCommented:
the inject may be a problem... the INIT code has to run in Type.pm (cause of the <DATA>) so i'm not shure...
to put it on CPAN you will have to properly package it and stuff. you can try, if you like...
as for the points... terageek wrote a very tough piece of code, he deserves points.
0

Featured Post

Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

  • 5
  • 3
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now