2013-08-24 6 views
0

У меня возникла проблема со следующим сценарием. Я пишу класс, который использует AUTOLOAD для вызова функций из другого модуля (не моего). Этот другой модуль использует несколько функций, в которых определены прототипы. Одним из примеров такой:Как обрабатывать внешний прототип с локальным AUTOLOAD в Perl

sub external_sub (\@$) { ... } 

Эти функции работают корректно при импорте непосредственно из этого модуля, с вызовами, как следующее:

my @arr = (1..10); 
external_sub(@arr, 'something else'); 

Теперь проблема у меня есть, когда требуя, чтобы внешний модуль из моего класса во время выполнения и импортирования этой функции, я не могу найти способ передать ему правильные аргументы.

Внутри моей AUTOLOAD функции, я рассчитываю на @_ только, и я не знаю, есть даже способ в Perl, чтобы отличить любой массив, передаваемый в качестве первого аргумента AUTOLOAD. Таким образом, внутри AUTOLOAD, единственные варианты, я думаю, до сих пор для перенаправления вызова являются:

no strict 'refs'; 
my $sym = *{"ExternalModule\::$called_sub"}; 
*$AUTOLOAD = $sym; 
goto &$AUTOLOAD; 

... или что-то вроде:

no strict 'refs'; 
return &{"ExternalModule\::$called_sub"}(@_); 

или несколько подобных вещей, с помощью таблицы символов. Однако проблема как передать аргументы этому звонку. Если в моем коде у меня есть:

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'); 

Тогда, это та точка, где AUTOLOAD в моем модуле потребуется внешний модуль и передать вызов к фактическому sub external_sub(\@$) прототипу. Проблема в том, что он будет принимать полученные аргументы как @_, где @arr и 'other_argument' теперь являются частью единого списка.

Есть ли способ решить такую ​​ситуацию? Есть ли способ определить, каковы первоначальные аргументы, прежде чем стать @_?

Имейте в виду, что я не контролирую внешний модуль и тот факт, что он использует прототипированную функцию.

Заранее благодарим за любые комментарии!

+2

Я думаю, что ваш единственный вариант - предустановить все автозагружаемые функции. Что-то вроде 'sub external_sub (\ @ $);' где-то доступно во время _compile time_. –

+0

Спасибо за комментарий. Возможно, вы правы. :-( –

ответ

2

Любому, имеющим подобную проблему, это то, что я нашел до сих пор:

  1. Вы можете определить только прототипы во время компиляции, в противном случае они игнорируются.

  2. Если вы знаете имя функции во время компиляции, но план по загрузке кода для символа позже (во время выполнения), то вы можете просто определить прототип без кода:

    sub some_sub(\@$); 
    
  3. Если вы не знаете имя функции, но вы можете получить его динамически ага времени компиляции, то вы можете использовать Scalar::Util::set_prototype объявить местный прототип только:

    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}; 
         } 
    } 
    

Поскольку только объявление прототипа готово, но не определение самой подпрограммы, когда эта подпрограмма вызывается в первый раз, она вызывает вашу подпрограмму AUTOLOAD, где вы можете перейти к назначению фактического символа coderef. Используемый вами coderef должен иметь тот же прототип, что и тот, который вы объявили этому имени, или вы получите сообщение об ошибке prototype mismatch. Можно назначить один и тот же прототип этому coderef, используя set prototype, если необходимо, как раз перед присвоением coderef действительному символу.

Например:

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; 
} 

Если кто-то знает о фактической способ изменить прототипы во время выполнения и есть их работа, как ожидается, после этого, я буду более чем счастлив знать об этом!

Между тем, я надеюсь, что это поможет любому, кто столкнется с подобной ситуацией в будущем.

 Смежные вопросы

  • Нет связанных вопросов^_^