Reputation: 3749
I'm having an issue with the following scenario. I'm writting a class that uses AUTOLOAD
somtimes to call functions from another module (not mine). This other module use a few functions where prototypes are defined. One example is this:
sub external_sub (\@$) { ... }
Those functions work correctly when imported directly from that module, with calls like the following:
my @arr = (1..10);
external_sub(@arr, 'something else');
Now, the problem I have when requiring that external module from my class at run time and importing that function, is that I can't find a way to pass the right arguments to it.
Inside my AUTOLOAD
function, I count on @_
only, and I don't know if there's even a way in Perl to tell apart any array passed as first argument to AUTOLOAD
. So, inside AUTOLOAD
, the only options I can think so far to redirect the call are:
no strict 'refs';
my $sym = *{"ExternalModule\::$called_sub"};
*$AUTOLOAD = $sym;
goto &$AUTOLOAD;
...or something like:
no strict 'refs';
return &{"ExternalModule\::$called_sub"}(@_);
or several similar things using the symbol table. However, the problem is how I pass the arguments to that call. If in my code I have:
package main;
use strict;
use MyModule qw(:some_external_subs); # This will import *names only but will decide later from which modules to take the actual code refs
# Here I have imported the sub 'external_sub' as symbol but it won't be actually loaded until MyModule::AUTOLOAD decides which external module will actually use to import the code for that function:
my @arr = ('some', 'values');
my $result = external_sub(@arr, 'other_argument');
Then, that's the point where AUTOLOAD
in my module will require an external module and pass the call to the actual prototyped sub external_sub(\@$)
. The problem is it will pass the received arguments as @_
, where @arr
and 'other_argument'
are now all part of a single list.
Is there any way to resolve a situation like this? Is there a way to detect what the original arguments were before becoming @_
?
Keep in mind I don't have control over the external module and the fact that it uses a prototyped function.
Thanks in advance for any comments!
Upvotes: 0
Views: 147
Reputation: 3749
To anyone having a similar problem, this is what I've found so far:
You can only define prototypes at compile time, otherwise they are ignored.
If you know the name of the function at compile time but plan on loading code for the symbol later (at run time), then you can just define the prototype without code:
sub some_sub(\@$);
If you don't know the name of the function but you can get it dynamically ar compile time, then you can use Scalar::Util::set_prototype
to declare the local prototype only:
package MyModule;
use strict;
use Scalar::Util qw(set_prototype);
my $protos;
BEGIN { # compile time
my @functions;
# Imagine you load here @functions with hashrefs containing name and proto values.
no strict 'refs'
for my $i (@functions) {
# This defines the prototype without actually defining the sub
set_prototype \&{"MyModule::$i->{name}"}, $i->{proto};
# If I want to recall the name/proto later in AUTOLOAD:
$protos->{$i->{name}} = $i->{proto};
}
}
Since only the declaration of prototype is ready but not the definition of the subroutine itself, when that subroutine is called for the first time it will trigger your AUTOLOAD
subroutine, where you can proceed to assign an actual coderef to the symbol. The coderef you use must have the same prototype as the one you declared to that name or you will receive an error of prototype mismatch
. It's possible to assign the same prototype to that coderef using set prototype
if necessary, just before assigning the coderef to the real symbol.
For example:
sub AUTOLOAD {
our $AUTOLOAD;
my $name = substr $AUTOLOAD, rindex($AUTOLOAD, ':') + 1;
# Here I do my process and decide that I'll call OtherModule::some_sub for the name in $AUTOLOAD
no strict 'refs';
my $coderef = *{"OtherModule::some_sub"}{CODE};
# Prototypes must match!
unless (defined(prototype $coderef) and $protos->{$name} eq prototype $coderef) {
set_prototype(\&$coderef, $protos->{$name});
}
*$AUTOLOAD = $coderef;
goto &$AUTOLOAD;
}
If someone else knows of an actual way to alter prototypes at runtime and have them work as expected after that, I'll be more than happy to know about it!
Meanwhile, I hope this helps anyone facing a similar situation in the future.
Upvotes: 2