我可以用Perl更改已加载模块中的代码行吗?

我可以用Perl更改已加载模块中的代码行吗?,perl,module,Perl,Module,当我使用该模块从多个FLV文件中提取元数据或合并多个FLV文件时,我经常收到“标记大小太小”错误,然后该模块将拒绝工作。有人在三年前发布了一个bug报告,但似乎没有解决方案 最近,我发现如果我只是在Tag.pm中注释掉以下代码行,FLV::Info的依赖模块之一,如下所示: =pod if ($datasize < 11) { die "Tag size is too small ($datasize) at byte " . $file->get_pos(-10)

当我使用该模块从多个FLV文件中提取元数据或合并多个FLV文件时,我经常收到“标记大小太小”错误,然后该模块将拒绝工作。有人在三年前发布了一个bug报告,但似乎没有解决方案

最近,我发现如果我只是在Tag.pm中注释掉以下代码行,
FLV::Info
的依赖模块之一,如下所示:

=pod
if ($datasize < 11)
   {
      die "Tag size is too small ($datasize) at byte " . $file->get_pos(-10);
   }
=cut
这种“从模具到非模具”的方法同样有效

BEGIN {
    *CORE::GLOBAL::die = sub { return CORE::die(@_) };
}
use FLV::Info;

{
    local *CORE::GLOBAL::die = sub {
         return if $_[0] =~ /^Tag size is too small/;
         return CORE::die(@_);
};

my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();
}
然而,“重新定义”方法并没有像我预期的那样有效。

我复制并粘贴了原始的FLV::Tag::parse子例程,并按照修改原始Tag.pm文件的方式注释了代码行,如下所示:

use FLV::Info;
no warnings 'redefine';
*FLV::Tag::parse = sub {
    ...
    ...
=pod
   if ($datasize < 11)
   {
      die "Tag size is too small ($datasize) at byte " . $file->get_pos(-10);
   }
=cut
   ...
   ...
};

my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();
好吧,即使复制并粘贴了完全相同的解析子例程,而没有对我的重新定义进行任何修改,我也会收到“未知标记类型”错误,而不是“标记大小太小”

真奇怪

作为参考,“重新评估”和“将模具覆盖到非模具”方法将提供以下信息:

1992 video frames
File name                sample.flv
File size                5767831 bytes
Duration                 about 79.6 seconds
Video                    1992 frames
  codec                  AVC
  type                   interframe/keyframe
Audio                    1712 packets
  format                 AAC
  rate                   44100 Hz
  size                   16 bit
  type                   stereo
Meta                     1 event
  audiocodecid           10
  audiosamplerate        22050
  audiosamplesize        16
  audiosize              342817
  creationdate           unknown
  datasize               805
  duration               79.6
  filesize               5767869
  framerate              25
  height                 300
  keyframes              {
    >>>                    'filepositions' => [
    >>>                                         '780',
    >>>                                         '865',
    >>>                                         '1324122',
    >>>                                         '2348913',
    >>>                                         '2978630',
    >>>                                         '3479001',
    >>>                                         '3973756',
    >>>                                         '4476281',
    >>>                                         '4997226',
    >>>                                         '5391890'
    >>>                                       ],
    >>>                    'times' => [
    >>>                                 '0',
    >>>                                 '0',
    >>>                                 '9.6',
    >>>                                 '19.2',
    >>>                                 '28.8',
    >>>                                 '38.4',
    >>>                                 '46.32',
    >>>                                 '55.92',
    >>>                                 '64.88',
    >>>                                 '73.88'
    >>>                               ]
    >>>                  }
  lastkeyframetimestamp  73.88
  lasttimestamp          79.6
  metadatacreator        Manitu Group FLV MetaData Injector 2
  metadatadate           1281964633858
  stereo                 1
  videocodecid           7
  videosize              5424234
  width                  400
最终更新

我已经弄明白了为什么“重新定义”方法通过启用strict和warnings pragma而失败。感谢@Schwern的提醒:)

首先添加以下代码行(从FLV::Util模块复制),然后重新定义FLV::Tag::parse子例程

Readonly::Hash our %TAG_CLASSES => (
   8  => 'FLV::AudioTag',
   9  => 'FLV::VideoTag',
   18 => 'FLV::MetaTag',
);

简单?不,但是你可以做一些疯狂的事情。这里有一些坏主意

其中一个更明显的方法是将一个被黑客攻击的.pm文件的副本放在你的项目中,这样它就可以在系统版本之前看到

另一种类似的方法是将整个例程剪切粘贴到代码中,并在加载原始例程后注入

use FLV::Tag;

no warnings 'redefine';
*FLV::Tag::parse = sub {
    ...copy of FLV::Tag::parse with your edits...
};
您可以覆盖
die
,使其在看到该消息时不会死亡

BEGIN {
    # In order to override die() later, you must override it at compile time.
    *CORE::GLOBAL::die = sub { return CORE::die(@_) };
}

{
    local *CORE::GLOBAL::die = sub {
        return if $_[0] =~ /^Tag size too small/;
        return CORE::die(@_);
    }

    ...do your thing...
}
您可以将该子例程的内容转储回Perl中,对代码执行字符串替换,然后重新计算它

use Data::Dump::Streamer;
my $original = FLV::Tag->can("parse");
my $code = Dump($original)->Out;
$code =~ s{\Qif ($datasize < 11)\E}{if( 0 )};

no warnings 'redefine';
*FLV::Tag::parse = eval $code;
使用数据::转储::拖缆;
my$original=FLV::Tag->can(“解析”);
my$code=转储($original)->输出;
$code=~s{\Qif($datasize<11)\E}{if(0)};
没有“重新定义”的警告;
*FLV::Tag::parse=eval$code;
或者您可以遍历该子例程的操作码树并更改条件,我将其作为练习留给手头有更多时间的人


这些都是坏主意。你最好只是修改代码,然后再次联系作者,让他们知道有新的信息。

好的,Schwern的回答非常彻底,但有一种方法不会那么“坏”

使用他的第二种方法(将整个例程剪切并粘贴到代码中,并在加载原始代码后注入它)。。。但是将其置于特定版本的
FLV::Info
(或
FLV::Tag
)上


这样,您仍然有您的monkey补丁(从技术上来说,这是一个monkey补丁吗?),但您删除了这种方法被认为是“不好”的最大原因之一,即,对模块的任何升级都可能与您的自定义补丁子例程冲突。如果您通过版本检查来保护覆盖,您就消除了这种担忧。

这是一个很好的答案,并附有一条很好的免责声明:)@Schwern,谢谢您与我分享这些“坏主意”!非常有帮助:)答案应该放在书签中,以便下次讨论Perl的“动态”属性的确切含义。@Schwern,我已经更新了我的文章,提供了更多细节。奇怪的是,如果我用与原始解析子例程完全相同的副本重新定义解析子例程,我将收到不同的错误消息,“未知标记类型”错误而不是“标记太小”错误。@Schwern,我已更新了我的帖子。问题解决了。谢谢你的提示:)这很有帮助!在掌握Perl时,我将深入介绍其中的一些内容。
BEGIN {
    # In order to override die() later, you must override it at compile time.
    *CORE::GLOBAL::die = sub { return CORE::die(@_) };
}

{
    local *CORE::GLOBAL::die = sub {
        return if $_[0] =~ /^Tag size too small/;
        return CORE::die(@_);
    }

    ...do your thing...
}
use Data::Dump::Streamer;
my $original = FLV::Tag->can("parse");
my $code = Dump($original)->Out;
$code =~ s{\Qif ($datasize < 11)\E}{if( 0 )};

no warnings 'redefine';
*FLV::Tag::parse = eval $code;