打印Perl对象方法的地址? (用于重新定义Perl类方法) [英] Printing addresses of Perl object methods? (for redefining Perl class methods)

查看:99
本文介绍了打印Perl对象方法的地址? (用于重新定义Perl类方法)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到了如何重新定义Perl类方法?,所以我想通过一个例子更好地了解它的工作原理. (相关:如何引用方法?-PerlMonks )

如果我有一个对象$obj,它是具有Class::method的某些Class的实例,那么$obj也具有$obj->method;我认为在这种情况下,简化的内存布局将看起来像这样(在 LaTeX表生成器中创建的表;对于以下内容,请此处为src ):

...即在(说)地址0x1000处有$obj->method,它只是(以某种方式)指向实际的Class::method定义,即(说)在0x3500.

比方说,那么我在主文件中的某个位置somefunc(忽略表中的前缀$)定义了sub,其定义最终位于地址0x2000.

如果我仅对$obj实例进行"monkey-patch"/替换方法,则希望内存布局看起来像这样( $ perl cltest.pl Starting Demo::Main... Orig test_me! 1: DMFo: 8e63dc0 DMFo->test_me 8e916f8 DMF::test_me 8e916e8 repl_test_me 8e91748 Orig test_me! -- CODE -- Subroutine Demo::My::Functions::test_me redefined at cltest.pl line 59. Repl test_me! Repl test_me! 2: DMFo: 8e63dc0 DMFo->test_me 8e916f8 DMF::test_me 8dfb708 repl_test_me 8dfb6c8

奇怪的是,即使在主文件repl_test_me中定义的函数也更改了地址-应该不应该!

因此,我显然没有打印我认为是函数地址的内容-而且我认为,每个调用有两个打印输出,而我应该只有一个打印输出,这一事实也得到了证实.

如何更改以下代码,以便打印出可以帮助我确认补丁/重载按预期发生的地址?

这是代码,cltest.pl:

use v5.10.1;

package Demo::My::Functions;
$INC{'Demo/My/Functions.pm'} = 1;

use warnings;
use strict;

use base 'Class::Accessor';

__PACKAGE__->mk_accessors(qw(test_me));

sub test_me {
  my $self = shift;
  print("Orig test_me!\n");
  return 1;
}

sub test_also_me {
  my $self = shift;
  print("Orig test_also_me!\n");
  return 1;
}

sub new {
  my $class = shift;
  my $self = {};
  bless $self, $class;
  return $self;
}

############################################################
package Demo::Main;


use warnings;
use strict;
print("Starting Demo::Main...\n");

my $DMFA = Demo::My::Functions->new();

sub repl_test_me {
  my $self = shift;
  print("Repl test_me!\n");
  return 1;
}

# note: \&{$DMFA->test_me} calls!
printf("1: DMFo: %p DMFo->test_me %p DMF::test_me %p repl_test_me %p\n",
      $DMFA, \&{$DMFA->test_me}, \&{'Demo::My::Functions::test_me'}, \&repl_test_me
);

print("-- " . ref(\&{$DMFA->test_me}) . " --\n");

{
no strict 'refs';
#~ *Demo::My::Functions::test_me = sub {my $self = shift; print("ReplIN test_me!\n"); return 1; }; # OK
#~ *Demo::My::Functions::test_me = *repl_test_me; # overloads
*Demo::My::Functions::test_me = \&repl_test_me; # overloads
};

# test it:
$DMFA->test_me();

# output addr again:
printf("2: DMFo: %p DMFo->test_me %p DMF::test_me %p repl_test_me %p\n",
      $DMFA, \&{$DMFA->test_me}, \&{'Demo::My::Functions::test_me'}, \&repl_test_me
);

解决方案

对象没有方法.类.一个对象只是一个变量,带有与之关联的包.

$ perl -MDevel::Peek -E'$o={}; Dump($o); bless($o); Dump($o); say \%main::'
SV = IV(0x26c2360) at 0x26c2370
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x269a978
  SV = PVHV(0x26a0400) at 0x269a978
    REFCNT = 1
    FLAGS = (SHAREKEYS)
    ARRAY = 0x0
    KEYS = 0
    FILL = 0
    MAX = 7
SV = IV(0x26c2360) at 0x26c2370
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x269a978
  SV = PVHV(0x26a0400) at 0x269a978
    REFCNT = 1
    FLAGS = (OBJECT,SHAREKEYS)
    STASH = 0x269a7f8   "main"
    ARRAY = 0x0
    KEYS = 0
    FILL = 0
    MAX = 7
HASH(0x269a7f8)    # Address of the main package.

所以真正的布局是

                +---------+
                |         |
                |         v
+-----------+   |   +-----------+
| Reference |   |   | 0x269a7f8 |
| 0x26c2370 |   |   | Package   |
+-----------+   |   +-----------+
      |         |         |
  References    |     Contains
      |         |         |
      v         |         v
+-----------+   |   +-----------+
| Hash      |   |   | 0xXXXXXXX |
| 0x269a978 |   |   | Method    |
+-----------+   |   +-----------+
      |         |
Blessed into    |
      |         |
      +---------+

更改程序包会影响使用该程序包的所有对象.

$ perl -E'
   sub f { "a" }
   my $o = bless({});
   say join " ", \&f, $o->can("f"), $o->f;
   *f = sub { "b" };
   say join " ", \&f, $o->can("f"), $o->f;
'
CODE(0x311c680) CODE(0x311c680) a
CODE(0x3126f60) CODE(0x3126f60) b

I saw How can I redefine Perl class methods?, so I wanted to understand better how it works through an example. (Related: How do I reference methods? - PerlMonks)

If I have an object $obj which is an instance of some Class which has Class::method, then $obj also has $obj->method; I assume the simplified memory layout in that case would look something like this (tables made in LaTeX Table Generator; for the below one, src here):

... that is, at (say) address 0x1000 we have the $obj->method, which would simply (somehow) point to the actual Class::method definition, which is (say) at 0x3500.

Let's say then I have a sub defined somewhere in the main file, somefunc (ignore the prefixed $ in the tables), whose definition ends up at address 0x2000.

If I "monkey-patch"/replace the method only for the $obj instance, I'd expect the memory layout to look something like this (src here):

Now the thing is this - in the example below (stuff is named more uniquely than in above tables), I actually want to replace the entire class method Demo::My::Functions::test_me with a method defined in the main file repl_test_me. I don't really know what to expect of addresses, so I'm trying to show what I think are addresses before and after the method patching, using the %p specifier of printf. The code outputs this:

$ perl cltest.pl 
Starting Demo::Main...
Orig test_me!
1: DMFo: 8e63dc0 DMFo->test_me 8e916f8 DMF::test_me 8e916e8 repl_test_me 8e91748
Orig test_me!
-- CODE --
Subroutine Demo::My::Functions::test_me redefined at cltest.pl line 59.
Repl test_me!
Repl test_me!
2: DMFo: 8e63dc0 DMFo->test_me 8e916f8 DMF::test_me 8dfb708 repl_test_me 8dfb6c8

What is weird here, is that even the function that is defined in the main file, repl_test_me, changes address - and it shouldn't?!

So, I'm apparently not printing what I think are addresses of functions - and I think it's also confirmed by the fact that I have two printouts per each call, while I should have only one?

How can I change the below code, so I print out the addresses that can help me confirm that the patching/overloading happened as expected?

Here is the code, cltest.pl:

use v5.10.1;

package Demo::My::Functions;
$INC{'Demo/My/Functions.pm'} = 1;

use warnings;
use strict;

use base 'Class::Accessor';

__PACKAGE__->mk_accessors(qw(test_me));

sub test_me {
  my $self = shift;
  print("Orig test_me!\n");
  return 1;
}

sub test_also_me {
  my $self = shift;
  print("Orig test_also_me!\n");
  return 1;
}

sub new {
  my $class = shift;
  my $self = {};
  bless $self, $class;
  return $self;
}

############################################################
package Demo::Main;


use warnings;
use strict;
print("Starting Demo::Main...\n");

my $DMFA = Demo::My::Functions->new();

sub repl_test_me {
  my $self = shift;
  print("Repl test_me!\n");
  return 1;
}

# note: \&{$DMFA->test_me} calls!
printf("1: DMFo: %p DMFo->test_me %p DMF::test_me %p repl_test_me %p\n",
      $DMFA, \&{$DMFA->test_me}, \&{'Demo::My::Functions::test_me'}, \&repl_test_me
);

print("-- " . ref(\&{$DMFA->test_me}) . " --\n");

{
no strict 'refs';
#~ *Demo::My::Functions::test_me = sub {my $self = shift; print("ReplIN test_me!\n"); return 1; }; # OK
#~ *Demo::My::Functions::test_me = *repl_test_me; # overloads
*Demo::My::Functions::test_me = \&repl_test_me; # overloads
};

# test it:
$DMFA->test_me();

# output addr again:
printf("2: DMFo: %p DMFo->test_me %p DMF::test_me %p repl_test_me %p\n",
      $DMFA, \&{$DMFA->test_me}, \&{'Demo::My::Functions::test_me'}, \&repl_test_me
);

解决方案

Objects don't have methods; classes do. An object is simply a variable with a package associated with it.

$ perl -MDevel::Peek -E'$o={}; Dump($o); bless($o); Dump($o); say \%main::'
SV = IV(0x26c2360) at 0x26c2370
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x269a978
  SV = PVHV(0x26a0400) at 0x269a978
    REFCNT = 1
    FLAGS = (SHAREKEYS)
    ARRAY = 0x0
    KEYS = 0
    FILL = 0
    MAX = 7
SV = IV(0x26c2360) at 0x26c2370
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x269a978
  SV = PVHV(0x26a0400) at 0x269a978
    REFCNT = 1
    FLAGS = (OBJECT,SHAREKEYS)
    STASH = 0x269a7f8   "main"
    ARRAY = 0x0
    KEYS = 0
    FILL = 0
    MAX = 7
HASH(0x269a7f8)    # Address of the main package.

So the real layout is

                +---------+
                |         |
                |         v
+-----------+   |   +-----------+
| Reference |   |   | 0x269a7f8 |
| 0x26c2370 |   |   | Package   |
+-----------+   |   +-----------+
      |         |         |
  References    |     Contains
      |         |         |
      v         |         v
+-----------+   |   +-----------+
| Hash      |   |   | 0xXXXXXXX |
| 0x269a978 |   |   | Method    |
+-----------+   |   +-----------+
      |         |
Blessed into    |
      |         |
      +---------+

Changing the package affects all objects that use that package.

$ perl -E'
   sub f { "a" }
   my $o = bless({});
   say join " ", \&f, $o->can("f"), $o->f;
   *f = sub { "b" };
   say join " ", \&f, $o->can("f"), $o->f;
'
CODE(0x311c680) CODE(0x311c680) a
CODE(0x3126f60) CODE(0x3126f60) b

这篇关于打印Perl对象方法的地址? (用于重新定义Perl类方法)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆