"Undefined subroutine" error (but it's defined, I think?)

Discussion in 'Perl Misc' started by valerian2@hotpop.com, Aug 9, 2003.

  1. Guest

    I have a program divided into 3 modules plus a main script, and some of
    the modules call functions within other modules. Most of the time this
    works fine, but occasionally I get an error like this:
    > Undefined subroutine &My::Misc::DB_Disconnect called at My/Misc.pm line 18.

    even though I made sure the caller (here Misc.pm) had imported all the
    functions of the other module. Hence, I don't understand the reason
    behind this error. I can get it to work fine by just replacing the
    DB_Disconnect() call in Misc.pm with &My::Misc::DB_Disconnect(), but
    that seems like a kludge, and I'd like to get a better understanding of
    what's going on. :)

    Here's what happens when I run the program (under perl 5.6.1):
    > $ ./test.pl
    > Error: /dev/null is full!
    > Disconnecting from DB...
    > Undefined subroutine &My::Misc::DB_Disconnect called at My/Misc.pm line 18.


    I isolated the problem to the "use My::SOAP;" line in DB.pm, but I need
    it there because some of the DB functions operate on SOAP data. So I
    left it there and poked around some more...

    I then isolated the real problem to be the "use My::Misc;" line in
    SOAP.pm, but again I need that line there, because the SOAP subs call
    a general untainting function in Misc.pm before returning the data.

    Here's the four files in question (I deleted everything but the bare
    minimum to reproduce this behavior):


    ----- ~/test.pl ---------------------------------
    #!/usr/bin/perl -w

    use strict;
    use My::DB;
    use My::Misc;

    my $dbh = DB_Connect();
    SafeError($dbh, '/dev/null is full!');


    ----- ~/My/DB.pm --------------------------------
    # Database functions
    package My::DB;

    use strict;
    use Exporter;
    use My::SOAP; # XXX this line causes the problem
    use vars qw(@ISA @EXPORT);
    @ISA = ('Exporter');
    @EXPORT = qw(DB_Connect DB_Disconnect);

    sub DB_Connect {
    # stub function, always returns true
    my $dbh = 1;
    return ($dbh);
    }

    sub DB_Disconnect {
    # stub function, always returns true
    my ($dbh) = @_;
    return (1);
    }

    1;


    ----- ~/My/Misc.pm ------------------------------
    # Miscellaneous functions (error handling, logging, untainting, etc.)
    package My::Misc;

    use strict;
    use Exporter;
    use My::DB;
    use vars qw(@ISA @EXPORT);
    @ISA = ('Exporter');
    @EXPORT = qw(SafeError);

    sub SafeError {
    my ($dbh, $msg) = @_;

    print "Error: $msg\n";

    if ($dbh) {
    print "Disconnecting from DB...\n";
    DB_Disconnect($dbh);
    }

    exit 1;
    }

    1;


    ----- ~/My/SOAP.pm ------------------------------
    # SOAP functions, for exchanging data with remote site
    package My::SOAP;

    use strict;
    use Exporter;
    use My::Misc; # XXX this line causes the problem
    use vars qw(@ISA @EXPORT);
    @ISA = ('Exporter');
    @EXPORT = qw(GetRemoteData);

    sub GetRemoteData {
    # stub function, always returns true
    return (1);
    }

    1;
     
    , Aug 9, 2003
    #1
    1. Advertising

  2. Bob Walton Guest

    wrote:

    > I have a program divided into 3 modules plus a main script, and some of
    > the modules call functions within other modules. Most of the time this
    > works fine, but occasionally I get an error like this:
    >
    >>Undefined subroutine &My::Misc::DB_Disconnect called at My/Misc.pm line 18.
    >>

    > even though I made sure the caller (here Misc.pm) had imported all the
    > functions of the other module. Hence, I don't understand the reason



    No you didn't. See comments at end.


    > behind this error. I can get it to work fine by just replacing the
    > DB_Disconnect() call in Misc.pm with &My::Misc::DB_Disconnect(), but


    DB------------------------------------------^^^^


    > that seems like a kludge, and I'd like to get a better understanding of
    > what's going on. :)

    ....
    > ----- ~/test.pl ---------------------------------
    > #!/usr/bin/perl -w
    >
    > use strict;
    > use My::DB;
    > use My::Misc;
    >
    > my $dbh = DB_Connect();
    > SafeError($dbh, '/dev/null is full!');
    >
    >
    > ----- ~/My/DB.pm --------------------------------
    > # Database functions
    > package My::DB;
    >
    > use strict;
    > use Exporter;
    > use My::SOAP; # XXX this line causes the problem
    > use vars qw(@ISA @EXPORT);
    > @ISA = ('Exporter');
    > @EXPORT = qw(DB_Connect DB_Disconnect);
    >
    > sub DB_Connect {
    > # stub function, always returns true
    > my $dbh = 1;
    > return ($dbh);
    > }
    >
    > sub DB_Disconnect {
    > # stub function, always returns true
    > my ($dbh) = @_;
    > return (1);
    > }
    >
    > 1;
    >
    >
    > ----- ~/My/Misc.pm ------------------------------
    > # Miscellaneous functions (error handling, logging, untainting, etc.)
    > package My::Misc;
    >
    > use strict;
    > use Exporter;
    > use My::DB;
    > use vars qw(@ISA @EXPORT);
    > @ISA = ('Exporter');
    > @EXPORT = qw(SafeError);
    >
    > sub SafeError {
    > my ($dbh, $msg) = @_;
    >
    > print "Error: $msg\n";
    >
    > if ($dbh) {
    > print "Disconnecting from DB...\n";
    > DB_Disconnect($dbh);
    > }
    >
    > exit 1;
    > }
    >
    > 1;
    >
    >
    > ----- ~/My/SOAP.pm ------------------------------
    > # SOAP functions, for exchanging data with remote site
    > package My::SOAP;
    >
    > use strict;
    > use Exporter;
    > use My::Misc; # XXX this line causes the problem
    > use vars qw(@ISA @EXPORT);
    > @ISA = ('Exporter');
    > @EXPORT = qw(GetRemoteData);
    >
    > sub GetRemoteData {
    > # stub function, always returns true
    > return (1);
    > }
    >
    > 1;
    >
    >


    Note in:

    perldoc -f use

    that use() calls require(). Note in

    perldoc -f require

    that require() keeps track of which modules have been loaded in %INC (a
    global variable, always in package main). Therefore, when the use()
    function is called, it will do something only if that module has not
    already been loaded. So each module will be loaded exactly once.
    Subsequent calls to use() will not do anything. Therefore, when you use
    Exporter; to export your sub's, it only happens during the first use()
    of your module. Thus, the export of your sub names happens only the
    first time a given modules is use()'d.

    Basically, you are misusing (pun intended) the module mechanism in an
    attempt to simply define subs. If all you want is for all your subs to
    be present even though they are defined in other files, simply do() the
    other files, and forget modules and packages. When you properly use
    modules, you do not share subs (in modules, properly called methods).

    From the docs for Exporter:

    "Do not export method names!

    Do not export anything else by default without a good reason!

    Exports pollute the namespace of the module user. If you must
    export try
    to use @EXPORT_OK in preference to @EXPORT and avoid short or common
    symbol names to reduce the risk of name clashes."

    You are trying to use use() wrong. Read up on the various
    object-oriented docs and buy "Object Oriented Perl" and read it.

    --
    Bob Walton
     
    Bob Walton, Aug 9, 2003
    #2
    1. Advertising

  3. Guest

    In article <>, Bob Walton wrote:
    >> even though I made sure the caller (here Misc.pm) had imported all the
    >> functions of the other module. Hence, I don't understand the reason

    >
    > No you didn't. See comments at end.


    Alright, I went and spent some intimate time with 'man perlmod', 'man
    perlmodlib', 'man Exporter', 'perldoc use', 'perldoc require', 'perldoc
    do', and all their friends... :)

    But none of those explain why the 'use DB;' line in Misc.pm is not
    importing the functions in the @EXPORT of DB.pm. The only way I can
    get this to work right is either by fully qualifying the function
    name (&My:DB:DB_Disconnect) or by inserting the line 'import My::DB;'
    somewhere in the My::Misc::SafeError sub. But adding the import line
    outside that scope doesn't work. That's what I don't understand. That
    sub belongs to the My::Misc package, so why doesn't it honor the pragmas
    in that package?

    And to make it even more confusing, in the 'real' modules (not the
    skeleton ones I listed previously) there are some subs that do honor the
    package pragmas, and some that don't. In both cases I'm talking about
    subs in the same package making identical calls to the same function in
    another package. Frex, in My::DB, several subs call My::Misc::GetDate,
    and in some cases I have to fully qualify it, and in some cases I don't.
    I can't seem to find a pattern though. :-(

    > Note in:
    >
    > perldoc -f use
    >
    > that use() calls require(). Note in
    >
    > perldoc -f require
    >
    > that require() keeps track of which modules have been loaded in %INC (a
    > global variable, always in package main). Therefore, when the use()
    > function is called, it will do something only if that module has not
    > already been loaded. So each module will be loaded exactly once.
    > Subsequent calls to use() will not do anything. Therefore, when you use
    > Exporter; to export your sub's, it only happens during the first use()
    > of your module. Thus, the export of your sub names happens only the
    > first time a given modules is use()'d.


    I rather like having everything exported as soon as possible. It's a
    mod_perl app, with all the modules preloaded by startup.pl, to avoid as
    many delays as possibly...

    I'm not sure what difference it makes that %INC is a global var though.
    That shouldn't negatively affect My::Misc, as the 'use DB;' line would
    just cause the 'require' step to be skipped and simply go on to import
    the @EXPORT subs in My::DB from the copy already in memory, right?

    > Basically, you are misusing (pun intended) the module mechanism in an
    > attempt to simply define subs. If all you want is for all your subs to
    > be present even though they are defined in other files, simply do() the
    > other files, and forget modules and packages. When you properly use
    > modules, you do not share subs (in modules, properly called methods).


    Well I only have a rudimentary understanding of OO Perl (having read the
    introductory OO chapter in Advanced Perl Programming--which incidentally
    I found easier to follow than the stuff in the camel book), so I'm not
    quite ready to take the plunge yet. And besides, I've already got well
    over 10,000 lines of procedural code that I have to finish testing ASAP,
    so there's no time to start tearing things up. Maybe next project... :)

    Still, I don't think it's a total waste to use modules for just plain
    old procedural code. I make judicious use of @EXPORT, @EXPORT_OK and
    use specific sub names based on the package name to avoid namespace
    clashes (frex, DB_Disconnect and other DB_ prefixed subs are in the
    My::DB package).
     
    , Aug 11, 2003
    #3
  4. wrote:
    [snip]
    > package My::DB;
    >
    > use strict;
    > use Exporter;
    > use My::SOAP; # XXX this line causes the problem
    > use vars qw(@ISA @EXPORT);
    > @ISA = ('Exporter');
    > @EXPORT = qw(DB_Connect DB_Disconnect);

    [snip function definitions]
    > # SOAP functions, for exchanging data with remote site
    > package My::SOAP;
    >
    > use strict;
    > use Exporter;
    > use My::Misc; # XXX this line causes the problem
    > use vars qw(@ISA @EXPORT);
    > @ISA = ('Exporter');
    > @EXPORT = qw(GetRemoteData);

    [snip function definition]

    Remember that 'use' happens at compile time, and @EXPORT= and @ISA=
    happen at run time.

    If you changed the beginnings of these three modules to:

    package My::DB;
    BEGIN {
    require Exporter;
    @ISA = qw(Exporter);
    @EXPORT = qw(DB_Connect DB_Disconnect);
    }
    use strict;
    use My::SOAP;

    And:

    package My::SOAP;
    BEGIN {
    require Exporter;
    @ISA = qw(Exporter);
    @EXPORT = qw(GetRemoteData);
    }
    use strict;
    use My::Misc

    And:

    package My::Misc;
    BEGIN {
    require Exporter;
    @ISA = qw(Exporter);
    @EXPORT = qw(SafeError);
    }
    use strict;
    use My::DB;

    Then it *should* work. [but since this code is untested, don't sue me
    if it doesn't.]

    --
    $a=24;split//,240513;s/\B/ => /for@@=qw(ac ab bc ba cb ca
    );{push(@b,$a),($a-=6)^=1 for 2..$a/6x--$|;print "$@[$a%6
    ]\n";((6<=($a-=6))?$a+=$_[$a%6]-$a%6:($a=pop @b))&&redo;}
     
    Benjamin Goldberg, Aug 12, 2003
    #4
  5. Bob Walton Guest

    Benjamin Goldberg wrote:

    > wrote:
    > [snip]
    >
    >>package My::DB;
    >>
    >>use strict;
    >>use Exporter;
    >>use My::SOAP; # XXX this line causes the problem
    >>use vars qw(@ISA @EXPORT);
    >>@ISA = ('Exporter');
    >>@EXPORT = qw(DB_Connect DB_Disconnect);
    >>

    > [snip function definitions]
    >
    >># SOAP functions, for exchanging data with remote site
    >>package My::SOAP;
    >>
    >>use strict;
    >>use Exporter;
    >>use My::Misc; # XXX this line causes the problem
    >>use vars qw(@ISA @EXPORT);
    >>@ISA = ('Exporter');
    >>@EXPORT = qw(GetRemoteData);
    >>

    > [snip function definition]
    >
    > Remember that 'use' happens at compile time, and @EXPORT= and @ISA=
    > happen at run time.
    >
    > If you changed the beginnings of these three modules to:
    >
    > package My::DB;
    > BEGIN {
    > require Exporter;
    > @ISA = qw(Exporter);
    > @EXPORT = qw(DB_Connect DB_Disconnect);
    > }
    > use strict;
    > use My::SOAP;
    >
    > And:
    >
    > package My::SOAP;
    > BEGIN {
    > require Exporter;
    > @ISA = qw(Exporter);
    > @EXPORT = qw(GetRemoteData);
    > }
    > use strict;
    > use My::Misc
    >
    > And:
    >
    > package My::Misc;
    > BEGIN {
    > require Exporter;
    > @ISA = qw(Exporter);
    > @EXPORT = qw(SafeError);
    > }
    > use strict;
    > use My::DB;
    >
    > Then it *should* work. [but since this code is untested, don't sue me
    > if it doesn't.]



    Hmmmmm...yes, that does make it work. But I don't think the explanation
    is quite right. @EXPORT and @ISA *do* get defined at compile time,
    because the use() is the same as BEGIN{require module;import module}.
    The require() do'es the module code, including the assignments to
    @EXPORT and @ISA, at compile time. And the import method runs at
    compile time. The do() from the use() will run before the import, so
    everything should be defined. BUT some of the modules are use'd twice,
    and the second time the do() is not performed. In the package of the
    second call to use(), @EXPORT and @ISA don't get defined, so the routine
    doesn't know it is an Exporter, so Exporter's import doesn't get called.
    And the @EXPORT list doesn't get exported. Your solution works
    because @ISA and @EXPORT are defined in the package namespace before the
    use() is called. I think that is right because I traced through it with
    the debugger with BEGIN{$DB::single=1} to debug the stuff executed at
    compile time.

    Pretty hairy. My apologies for my incorrect first analysis, as pointed
    out by Abigail and valerian2.
    --
    Bob Walton
     
    Bob Walton, Aug 12, 2003
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    0
    Views:
    616
  2. Oodini
    Replies:
    1
    Views:
    1,784
    Keith Thompson
    Sep 27, 2005
  3. Dan Burke
    Replies:
    6
    Views:
    207
    Dan Burke
    Jul 5, 2004
  4. Replies:
    10
    Views:
    283
    Gunnar Hjalmarsson
    Aug 11, 2005
  5. it_says_BALLS_on_your forehead
    Replies:
    0
    Views:
    360
    it_says_BALLS_on_your forehead
    Jan 24, 2006
Loading...

Share This Page