• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 274
  • 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
Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
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
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Get your problem seen by more experts

Be seen. Boost your question’s priority for more expert views and faster solutions

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