【问题标题】:Passing object between programs running under different perl versions在不同 perl 版本下运行的程序之间传递对象
【发布时间】:2020-07-22 18:14:22
【问题描述】:

在使用从 perl5.6.pl 到 perl5.24.pl 的不同 perl 版本将对象作为输入参数传递时遇到问题(无法从函数“$from_5.24”获取返回值)。在有问题的代码下方提供。使用windows平台,如何解决这个问题。

SharedBetweenPerls.pm:

package SharedBetweenPerls;
use warnings;
use strict;
use Exporter;
our @ISA = 'Exporter';
our @EXPORT_OK = qw(getVal);

sub new {
   my $self = {
      roleId => undef,
      username => undef,
   };
   bless $self, 'SharedBetweenPerls';
   return $self;
}

sub getVal{
  my ($self) = @_;
  return $self->{'roleId'};
}
1;

v5.24.pl:

use warnings;
use strict;
use v5.24;
use lib '.';
my ($self) = @_;
print $self->{'roleId'}; # Not working

v5.6.pl:

use warnings;
use strict;
use lib '.'; 
use SharedBetweenPerls;

my $obj =  new SharedBetweenPerls();
$obj->{'roleId'} = '10000';
$obj->{'username'} = 'test123';

my $roleId = $obj->getVal();
print "Value : $roleId \n"; # working fine

my $from_5.24 = qx(path-to-perl-5.24 program_for_5.24.pl "$obj"); 
print "Return from function: $from_5.24"; # Not Working

我尝试过使用 zdim 提供的序列化测试代码 (SharedBetweenPerls.pm)。我收到了以下格式错误。

malformed JSON string, neither tag, array, object, number, string or atom, at character offset 0 (before "roleId") at SharedBetweenPerls.pm

5.6.pl:

use warnings;
use strict;
use lib '.'; 
use SharedBetweenPerls;
use IPC::System::Simple qw(capturex);
#my $data = '{"roleId":31, "username":"test123"}';
#my $obj = SharedBetweenPerls->new($data);
my $obj = SharedBetweenPerls->new(roleId => 17, username => 'test123');
my $result = capturex('D:\Perl\bin\perl524.exe', 'D:\sample_program\p5.24.pl', '$obj->serialize');
print "return from function: $result";

5.24.pl:

use warnings;
use strict;
use v5.24;
use lib '.';
use SharedBetweenPerls;
my ($init_json) = @ARGV;
my $obj = SharedBetweenPerls->new( $init_json );
print $obj->{'roleId'};

【问题讨论】:

  • 子存在于解释器中。这包括方法。所以类也存在于解释器中。对象是类的实例,因此传输对象需要传输一个类,除非它已经加载到您要传输对象的解释器中。由于您之前已明确表示不能这样做,因此您不能转移对象。
  • 能否分享将对象从一个 perl 版本转移到另一个 perl 版本的可行性。
  • 我只是解释了你不能,除非你可以在两个解释器中加载类模块。你能做到吗?
  • 您能解释一下如何在两个解释器中加载类模块吗?将对象从 5.6 转移到 5.24 的原因是调用支持 TLS 1.2 的 Web 服务,并且输出需要从 5.24 转移到 5.6。在当前程序中,相同的对象用于输入和输出模块。
  • 没有。管道只能传输字节序列,我们用字符串表示。其他形式的数据需要发送方序列化(转换为字节序列),接收方反序列化。查看 zdim 的回答

标签: perl serialization


【解决方案1】:

注意   下面有两种序列化方法——使用Storable(可能会被命中或错过,因为它需要在不同的 Perl 和模块版本之间),以及一种自定义的


这是previous question 中提出的问题的更一般情况,现在要在程序之间传递对象。这种变化带来了实质性的不同。

一个对象必须是serialized才能被传递,这样我们就可以通过某种管道逐字节地传递它。我在此演示中为此使用了Storable,但您可能需要寻找其他工具或编写自定义流程(在末尾添加的部分中进行了演示

还有一些其他调整,将在下面讨论,这里是文件。

SharedBetweenPerls.pm

package SharedBetweenPerls;    
use warnings;
use strict;

sub new {
    my ($class, %args) = @_; 
    my $self = { %args };
    return bless $self, $class;
}

sub get_roleId {
    my ($self) = @_; 
    return $self->{'roleId'};
}

1;

需要在v5.24(v.5.24.pl)下运行的程序

use warnings;
use strict;

use Storable qw(retrieve);

use v5.24;    

use FindBin qw($RealBin); 
use lib $RealBin;       # look for modules in this script's directory
use SharedBetweenPerls;

my ($file) = @ARGV;

my $obj = retrieve($file) // warn "There were errors: $!";

print $obj->get_roleId;

“主”程序,必须在旧版 perl 下运行

use warnings;
use strict;
use feature 'say';

use Storable qw(store);

use FindBin qw($RealBin); 
use lib $RealBin;   
use SharedBetweenPerls;

my $obj = SharedBetweenPerls->new(roleId => 17, username => 'test123');

my $roleId = $obj->get_roleId();
say "Value for 'roleId' in the new object: $roleId";

my $outfile = "obj_$$.storable";  # store the serialized object in a file
store($obj, $outfile) // warn "There were errors: $!";                 #/

# (replace my perlbrew path with your actual path to the v5.24 executable)
my $perl_524 = qq($ENV{HOME}/perl5/perlbrew/perls/perl-5.30.0/bin/perl);
my $cmd = qq($perl_524 v5.24.pl $outfile);
my $from_524 = qx( $cmd );
chomp $from_524;    
say "Return from function: $from_524";

unlink $outfile or warn "Can't unlink $outfile: $!";  # can delete file now

写入序列化对象的文件的名称应该比我在此演示中使用的名称好得多,File::Temp 是处理临时名称的标准选择。

打印出来

新对象中“roleId”的值:17 从函数返回:17

所以对于这个简单的玩具类来说,这是可行的——对象被正确传递了。

然而,序列化绝不是一件小事。根据您的实际课程的复杂程度,特别是两个程序的模块版本的不同,可能会出现问题。对于 v5.6 和 v5.24 的组合,我认为您需要保持手指交叉。 (它适用于我的 v5.16 和 v5.30,但 v5.6 非常非常旧。)

store+retrieve 的组合是(一种方式)使用文件传递复杂数据的方式。我还尝试freeze 对象并手动将其写入文件,然后读取该文件并thaw 它,效果也很好。 (将冻结的物体直接沿管道传递很麻烦。)

但是传递整个对象可能不起作用,如果您的情况确实存在问题,那么该怎么做将完全取决于您的类实际上是什么样的。

您始终可以做的一件事是提出一种自定义序列化方法,通过这种方法传递所需的数据(通过文件或为管道适当地序列化),而不是整个对象。然后另一端的程序可以使用它来构造对象。

如果使用文件传递数据,那么明确的选项是什么,那么我们正在谈论persistence

评论

  • 当一个包定义一个类时,没有理由导出符号

  • 不要对包名进行硬编码;有__PACKAGE__。但是对于一个类,包名作为构造函数中的第一个参数传递,应该使用它

  • 不要将对象用作任何旧的 hashref,在这种情况下,只需取消引用键即可打印值。这戳中了一个类的内部结构,这是一个非常非常糟糕的主意——使用提供的方法。 (为此,v.5.24.pl 程序也需要使用类加载包)

  • 如果您希望被调用的程序能够使用一个对象,它应该加载定义该类的包(因为不应将该对象用作单纯的 hashref)

  • indirect method notation (new ClassName) 非常适合避免使用。请改用普通的方法调用 (ClassName->new)。一方面,构造函数一个方法

  • 程序的参数在@ARGV,而不是@_

  • 上面的课程需要更多,但这会把我们带到别处

  • 我建议使用模块来运行外部命令,而不是反引号 (qx)。尝试一些:IPC::System::SimpleCapture::TinyIPC::Run3IPC::Run


一个例子

为您的类添加一个实现所需反序列化的方法。它可以简单地创建一个包含所有属性及其值的散列,然后对其进行序列化——例如从中生成一个 JSON 字符串。 (如果某些属性的值是其他类的对象,那么您必须做更多的工作。)

那么v5.6.pl程序就可以了(这里使用IPC::System::Simple模块)

use IPC::System::Simple qw(capturex);
...
my $from_524 = capturex($perl_524, 'v5.24.pl', $obj->serialize);

然后v.5.24.pl 程序就可以做

my ($init_json) = @ARGV;

my $obj = SharedBetweenPerls->new( $init_json );

所以现在目标程序有一个用所需数据构建的对象,可以开始工作了。

这是一个非常简单粗暴的例子请注意,对于您的项目,这可能是一个糟糕的选择,我对此一无所知,而如果它可以使用,那么它需要更多的工作/检查。

我使用 JSON 来序列化属性及其值的哈希值;这是在serialize 方法中完成的。然后这样的 JSON 字符串可以用来构造一个新对象:构造函数检查它是否得到一个 hashref 或一个字符串,对于一个字符串它调用一个方法 (init_from_json) 来初始化对象。

sub new {                              # WARNING: a sketchy demo only
    my ($class, $args) = @_; 
    my $self = {}; 
    bless $self, $class;
    my $ref = ref $args;
    if (not $ref) {                    # a string; better be JSON
        $self->init_from_json($args);
    }   
    elsif ($ref eq 'HASH') {           # straight-up attributes, initialize
        $self->{$_} = $args->{$_}  for keys %$args;
    }   
    else { croak "Unsupported invocation..." }  # print user message etc
    return $self;
}

sub serialize {
   my $self = shift;
    require JSON; JSON->import('encode_json');
    my %attr = map { $_ => $self->{$_} } keys %$self;
    return encode_json(\%attr);  # (no objects please)
}

sub init_from_json {
    my ($self, $args_json) = @_; 
    require JSON; JSON->import('decode_json');
    my $args = decode_json($args_json);
    $self->{$_} = $args->{$_} for keys %$args;
}

...

现在v5.6.pl 程序可以创建它的对象并对其进行序列化,然后调用v5.30.pl 程序并将该JSON 字符串作为输入传递给它。然后v5.30.pl 程序可以从 JSON 重建对象并使用它进行工作。

还有很多其他方法可以做到这一点,具体取决于具体情况。

如果您要为 OO 代码使用框架,例如 MooseMoo,那么有现成的工具和技术会有所帮助。 (如果您还没有使用过这些框架,那么还有一些学习曲线。)


按照要求,完整的工作程序(最大限度地简化以帮助调试) -

v5.6.pl

use warnings;
use strict;
    
use IPC::System::Simple qw(capturex);

use FindBin qw($RealBin); 
use lib $RealBin;   
use SharedBetweenPerls;

my $obj = SharedBetweenPerls->new( { roleId => 17, data => [3..7] } );

# (replace my perlbrew path with your actual path to the v5.24 executable)
my $perl_524 = qq($ENV{HOME}/perl5/perlbrew/perls/perl-5.30.0/bin/perl);

my $from_524 = capturex( $perl_524, 'v5.30.pl', $obj->serialize );
print "Return from function: $from_524";

v5.30.pl

use warnings;
use strict;

use v5.24;
use FindBin qw($RealBin); 
use lib $RealBin;   
use SharedBetweenPerls;

my ($init_json) = @ARGV;

my $obj = SharedBetweenPerls->new($init_json);
print $obj->get_roleId;

【讨论】:

猜你喜欢
  • 2012-02-10
  • 1970-01-01
  • 2021-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多