challenge メソッドのフック

例えば、あるクラスのあるメソッドを実行する前に他の処理を呼びたい(例えばログやトランザクション開始など)。 また、そのメソッドの終了後にも何らかの後処理を呼びたい(トランザクション終了など)。

そのような、メソッドに対するフック処理を書いてください。 ライブラリを使用してメソッドのフックを実現した場合は ライブラリの名前を紹介してくれると助かります。

Posted feedbacks - Flatten

Nested Hidden

そういう特定の環境やパラダイムに特化したお題はこの場にふさわしくないと思いますが。 そういうお題ならcodeなにがしの方が向いているのでは?


こんなでいいですか?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
function addHock(obj, method_name, before, after) {
  var orignal = obj[method_name];
  obj[method_name] = function() {
    var error, ret;
    before.apply(this, arguments);
    try {
      ret = orignal.apply(this, arguments);
    } catch (e) {
      error = e;
    }
    after.call(this, arguments, ret, error);
    if (error) throw error;
    return ret;
  };
  return orignal;
}

addHock(String.prototype, "split", 
    function(ptn){alert("split:"+ptn);}, 
    function(args,ret,e){alert("return:"+ret);});

"hogehoge".split("g");

C#ってデフォルト引数使えないの初めて知った・・・。
仕方ないのでnullかどうかで判別
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System;

delegate void func();

class OpenFile
{
    public OpenFile() { }

    public void readFile(string filename,
                         func begin,
                         func end)
    {
        if (begin != null)
            begin();
        else
            dummy();

        try
        {
            Console.WriteLine(
                new System.IO.StreamReader(
                    filename
                ).ReadToEnd()
            );
        }
        catch
        { 
        
        }

        if (end != null)
            end();
        else
            dummy();
    }
    private void dummy() { Console.WriteLine("dummy!"); }
    public void Begin() { Console.WriteLine("Begin!"); }
    public void End() { Console.WriteLine("End!"); }
}

class Program
{
    static void Main(string[] args)
    {
        OpenFile of = new OpenFile();
        string filename = @"C:\a.txt";
        
        of.readFile(filename, null, null);
        Console.WriteLine();
        of.readFile(filename, of.Begin, null);
        Console.WriteLine();
        of.readFile(filename, null, of.End);
        Console.WriteLine();
        of.readFile(filename, of.Begin, of.End);
        Console.WriteLine();  
    }
}

空気を読まんと投稿。 java.lang.reflectパッケージを使用します。 InvocationHandlerインタフェースのinvoke()メソッドで前処理、後処理を実装します。 Proxy.newProxyInstance() で受け取ったインスタンスのメソッドを実行したときに、 InvocationHandler.invoke()が動作します。

インタフェースに対してのみ適用可能であることをお忘れなく。

実行結果。

proxy = class test.hook.$Proxy0

method called. name = execute

ServiceImpl.execute() called.

method returned.

return value = Hello, world.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package test.hook;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Hook {

    public Hook() {
        InvocationHandler handler = new TestInvocationHandler(new ServiceImpl());
        Service service = (Service) Proxy.newProxyInstance(ServiceImpl.class.getClassLoader(), new Class[]{Service.class}, handler);

        System.out.println("return value = " + service.execute());
    }

    public static void main(String[] args) {
        new Hook();
    }
}

/** サービスのインタフェース */
interface Service {

    public String execute();
}

/** サービスの実装クラス */
class ServiceImpl implements Service {

    public String execute() {
        System.out.println("ServiceImpl.execute() called.");
        return "Hello, world.";
    }
}

/** メソッドのフックに使用するハンドラ */
class TestInvocationHandler implements InvocationHandler {

    private Service service;

    public TestInvocationHandler(Service service) {
        this.service = service;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result;
        System.out.println("proxy = " + proxy.getClass().toString());
        System.out.println("method called. name = " + method.getName());  //前処理
        result = method.invoke(service, args);  //実際の処理
        System.out.println("method returned.");  //後処理
        return result;
    }
}

メソッドって言うか, 関数に対して. (勿論メソッドにも適用可能)

無い関数に対しては, 勝手に作っちゃうけど良いっすか?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use warnings;
use strict;

{
  my %hooks   = ();

  sub add_hook($$&;$){
    my ($class, $meth, $sub, $is_after) = @_;
    my $key = "$class\0$meth";
    my $orig = UNIVERSAL::can($class, $meth) || sub(@){};
    unless(exists $hooks{$key}){
      my $pre   = [];
      my $after = [];
      my $hook  = [$pre, $after];
      $hooks{$key} = $hook;
      {
        no strict;
        no warnings;
        my $proto = defined(prototype $orig) ? "(".prototype($orig).")" : "";
        *{"${class}::${meth}"} =  eval qq{ sub ${proto}{
          use strict;
          foreach my \$fun (\@\$pre)
            {
              \$fun->();
            }

          my \@R = ();
          my \$R = undef;
          if(wantarray)
            {
              \@R = \$orig->(\@_);
            }
          else
            {
              \$R = \$orig->(\@_);
            }

          foreach my \$fun (\@\$after)
            {
              \$fun->();
            }
          wantarray ? \@R : \$R;
        } };
      }
    }
    push @{$hooks{$key}->[$is_after ? 1 : 0]}, $sub;
  }
}


# TEST
use Data::Dumper;
{
  package hoge;
  sub hemo(){
    print "HEMO\n";
    (wantarray ? (1,2,3) : [1,2,3])
  }
}

add_hook(hoge => hemo => sub(){
           print "pre1\n";
         });
add_hook(hoge => hemo => sub(){
           print "pre2\n";
         });

add_hook(hoge => hemo => sub(){
           print "after1\n";
         },1);
add_hook(hoge => hemo => sub(){
           print "after2\n";
         },1);

print join "-", hoge::hemo;
print "\n\n\n";
print Dumper scalar hoge::hemo;

CLOS だと、単に:before, :after で追加定義するだけ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(defclass name () ())

(defmethod say ((n name) string)
  (format t "~A" string))
  
(defmethod say :before ((n name) string)
  (princ "Hello,"))
(defmethod say :after ((n name) string)
  (princ " speaking."))

(say (make-instance 'name) "Bobby")
;Hello,Bobby speaking.

やってもうた。ハズカシス。 ×addHock → ○ addHook


Squeak Smalltak で。AOP 拡張を施す AspectS というパッケージを用います(Squeak3.7 用)。

AsAspect を継承した AsFoo に次のメソッドを定義します。この例では、クラス Foo のインスタンスに #bar というメソッドを起動させたとき、トランスクリプトに時刻を出力します。

使用例:
| aspect |
aspect := AsFoo new.
aspect install
Foo new bar.   "=> 時刻出力"
aspect uninstall
1
2
3
4
5
AsFoo >> adviceBefore
    ^ AsBeforeAfterAdvice
        qualifier: (AsAdviceQualifier attributes: #(receiverClassSpecific))
        pointcut: [{AsJoinPointDescriptor targetClass: Foo targetSelector: #bar}]
        beforeBlock: [:rcvr :args :aspect :client | Transcript cr; show: Time now]

#6029を参考にしました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def add_hook( klass, method, before, after )
    klass.instance_eval do
        original = instance_method( method )
        define_method( method ) do |args|
            before.call( self, args )
            begin
                result = original.bind( self ).call( *args )
            rescue Exception => e
                after.call( self, args, result, e )
            else
                after.call( self, args, result, nil )
            end
            result
        end
    end
end

add_hook( String, :split, proc{|s,*|puts "split:#{s}"},
    proc{|s,a,r,e|puts"return:#{r.inspect}"} )

p 'hoge'.split(//)

あ、rescueした後、再度同じ例外を発生されるのを忘れていました><
rescue Exception => e
    after.call( self, args, result, e )
の後にraiseをつけます。

すいません、さらにもう一つ。

define_method( method ) do |args|
は引数が0個や1個の場合でも配列で得るように
define_method( method ) do |*args|
の方がよいですね。

before/after は各 setter 経由で渡す。 なんか他の見てると、題意から外れてる気がしなくもない・・。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class HookSample {

    private Runnable before = new Runnable() { 
        public void run() { }
    };
    private Runnable after = new Runnable() {
        public void run() { }
    };

    public void call() {
        before.run();
        process();
        after.run();
    }

    protected void process() {
        System.out.println("main process...");
    }

    public void setBefore(Runnable r) {
        if (r != null) {
            before = r;
        }
    }

    public void setAfter(Runnable r) {
        if (r != null) {
            after = r;
        }
    }

}

Cのマクロです。
define を関数定義の前にするとコンパイル通りません。
なんとか戻り値を取りたくて or にしました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

int func(int n, char *s)
{
  printf("n = %d  s = %s\n", n, s);
  return n;
}

int before()
{
  puts("入るよー");
  return 0;
}
int after()
{
  puts("出たよー");
  return 0;
}

#define func(n,s) (before() | func(n,s) | after())

int main(void)
{
  int ret = 0;
  ret = func(3, "文字列");
  printf("戻り値:%d\n", ret);
  return 0;
}

その書き方ではCの言語仕様上では必ずbefore,func,afterの順に呼び出されることを保証できないのではないでしょうか。 評価が記述されている順番に行われることが保証されているのは条件演算子(?:),論理和演算子(||),論理積演算子(&&),カンマ演算子(,)のみだったと記憶しています。 (カンマ演算子以外は必ずしも全ての被演算子が評価されるとは限らないのでこのように列挙するのは微妙な気もしますが)

こんにちは。

C++で関数テンプレートです。 方法は、スレッドにすでに投稿されてる方々の方法を参考にしています。 実際本気でやろうと思ったら、OSのAPI使うか、アセンブリいじる羽目になるので低級なほどシビアですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
///////////////////////////////////////////
class Functer{
    char* str_;
public:
    Functer(char* str){str_ = str;}
    int operator ()(){
        printf("%s",str_);
        return 0;
    }
};
///////////////////////////////////////////
int func(){
    printf("I am Main Process!\n");
    return 0;
}
///////////////////////////////////////////
template<class F1,class F2,class F3>
int Call(F1& Before,F2& Main,F3& After){
    int ret;
    Before();
    ret = Main();
    After();
    return ret;
}
////////////////////////////////////////////
int main(){
    Functer F1("Before Process!\n"),F3("After Process!!\n");
    Call(F1,func,F3);//関数形式で呼べれば何も問題ない。はず。
    return 0;
}

あ、ほんとだ。
左からの評価が保証されるのは、&& 、 || 、カンマ演算子の「 , 」、三項演算子の「 :? 」だけみたいですね。(参考ページより)
gcc でコンパイルしてサクッと動いたからよしとしてしまいました。
コンマで戻り値を返すにはどうしたらよいでしょう?
Cだとその場で変数を宣言できないし…。

参考ページを貼り損ねてしまった・・・


題意を、呼び側も呼ばれ側も改造する事なく、開始終了のフックを行う事と解釈しました。

アスペクト指向を Java 上で実現する AspectJ を用いてメソッドの開始と終了にフックを行うサンプルです。下記の Sample クラスの printMessage メソッドに対するフックを行います。

public class Sample {
    public static void main(String[] args) {
        new Sample().printMessage("Hello, world.");
    }

    public void printMessage(String message) {
        System.out.println(message);
    }
}

言語は AspectJ と言った方が良いかも知れません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public aspect Trace {

    pointcut message(): call(* Sample.printMessage(..));

    before(): message() {
        System.out.println("*** begin of printMessage() ***");
    }

    after(): message() {
        System.out.println("*** end of printMessage() ***");
    }
}

メソッド限定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Hoge(object):
    def process(self):
        print 'Hoge::process'

class HogeHook(Hoge):
    def process(self):
        print 'begin HogeHook::process'
        super(HogeHook, self).process()
        print 'end HogeHook::process'

Hoge = HogeHook

a = Hoge()
a.process()

#6044(Cの関数フックもどき)について補足。
関数の戻り値を返せるようにするには、グローバル変数を使うのが簡単そうです。
または、afterで関数の戻り値を参照できたほうが便利なので、
( before(), after(func()) )
とするとよいかも。afterは引数をそのまま返します。

Perlの関数アトリビュートを使ってみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/perl

use strict;
use warnings;

package HookAttribute;

use Attribute::Handlers;

sub Hook: ATTR(CODE) {
  my (undef, undef, $referent, undef, $args) = @_;
  $args = [$args] unless ref($args) eq "ARRAY";
  my $hook_before = shift(@$args) eq "Before";

  no strict "refs";
  no warnings "redefine";
  for my $funcname (@$args) {
    next unless defined *{$funcname}{CODE};

    my $funcref = \&{$funcname};
    *$funcname = $hook_before
               ? sub { $referent->(@_); goto &$funcref }
               : sub { $funcref->(@_); $referent->(@_) }; #callerなどが狂う
  }
}

#テストコード
package Foo;

use Perl6::Say;

sub new { bless {}, shift }
sub foo { say "foo" }

package FooHooks;

use base qw/HookAttribute/;
use Perl6::Say;

sub bar: Hook("Before", qw/Foo::foo/) { say "bar" }
sub baz: Hook("After", qw/Foo::foo/) { say "baz" }

Foo->new->foo;

traitを使ってみました。フックを差し込むクラスは finalでない必要があります。 参考URL: http://jonasboner.com/2008/02/06/aop-style-mixin-composition-stacks-in-scala/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
trait ReaderInterceptor {
  def read() :Int
}

trait ReaderInterceptorExample extends ReaderInterceptor {
  abstract override def read() :Int = {
    val result = super.read
    if(result != -1) println("character: " + result.toChar)
    result
  }
}

object Main extends Application {
  import java.io._
  val reader = new StringReader("ABCDE") with ReaderInterceptorExample
  var ch :Int = 0
  while({ch = reader.read; ch != -1}){}
}

decoratorを使ってmultithreadでクリティカルセクションを守るコードです。try/finallyを使っているのでlockのreleaseもれもないはずです。 accessorでどのようにlockにアクセスするか指定できます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def synchronized_with(accessor):
  def bind(critical_section):
    def synchronized(self, *args, **kw):
      lock = accessor(self, *args, **kw)
      lock.acquire()
      try:
        ret = critical_section(self, *args, **kw)
      finally:
        lock.release()
      return ret
    return synchronized
  return bind

デコレータを使って。トランザクションとのことなので、一応例外が出ても "after" が表示されることを確認。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def test(f):
    def wrap(*a, **kw):
        print "before"
        try:
            return f(*a, **kw)
        finally:
            print "after"
    return wrap

class Foo:
    @test
    def foo(self, x, exc=False):
        print x
        if exc:
            raise RuntimeError("foo")

if __name__ == '__main__':
    Foo().foo(3)
    Foo().foo(4, exc=True)

どう使うかを追加。でないとpythonのdecoratorをしらない人に不親切すぎ。

fooの中身がlock不要に見えるのは気にしないでください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import threading

class class_level_lock:
  _lock = threading.RLock()
  lock = lambda self, *args, **kw : self._lock

  @synchronized_with(lock)
  def foo(self, x):
    return x*2

class instance_level_lock:
  lock = lambda self, *args, **kw : self._lock
  def __init__(self):
    self._lock = threading.RLock()

  @synchronized_with(lock)
  def foo(self, x):
    return x*2

Haskellでは、フックするということを、関数に前処理、後処理を追加すると読み替えることになるのかなぁ。いくつかのパターンがありそうだけど、とりあえず、一般の関数、モナドの例をあげておきましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Control.Monad

addBeforeHook :: (a -> a) -> (a -> b) -> (a -> b)
addAfterHook  :: (b -> b) -> (a -> b) -> (a -> b)

addBeforeHook h f = f . h
addAfterHook  h f = h . f

addBeforeHookM :: Monad m => (a -> m a) -> (a -> m b) -> (a -> m b)
addAfterHookM  :: Monad m => (b -> m b) -> (a -> m b) -> (a -> m b)

addBeforeHookM h f x = h x >>= f
addAfterHookM  h f x = f x >>= h

Functionを拡張すれば簡単なはずだ、という当初の妄念が色濃く残ったコードと相成りました。

Linux版のFirefox,同Opera weekly buildで動作を確認。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
String.prototype.hook=function(fnBefore,fnAfter,scopeMagic){
    var target=this;
    var str=[
        "var orig=",target,";\n",
            target,"=function(){\n",
            ,((fnBefore) ? ["(",fnBefore,").apply(this,arguments);"].join("") : ""),
            ,"\nvar ret=orig.apply(this,arguments);\n",
            ,((fnAfter) ? ["(",fnAfter,").apply(this,arguments);"].join("") : ""),
            "\nreturn ret;}"
    ].join("")
    if(scopeMagic){
        eval(str.replace(new RegExp(target,"g"),"scopeMagic"));
        return scopeMagic;
    }else{
        eval(str);
    }
}

// グローバル空間
var aa=[3,2,4];
"aa.sort".hook(
    function(){
        alert("global: before: "+this);
    }
    ,function(){
        alert("global: after: "+this);
    }
);
aa.sort();

// どっかのスコープ内
(function(){
    var a=[1,6,2,3];

    a.sort="a.sort".hook(
        function(){
            alert("before: "+this);
        }
        ,function(){
            alert("after: "+this);
        }
        ,a.sort
    );

    a.sort();
})();

RTTIの仮想関数テーブルを書き換えてみます。
D言語では明示的にfinalと指定しない限り、外から見えるメソッドはすべて仮想関数ですが、最適化によってインライン化された場合はダメかもしれません。
Testクラスのvtblを書き換えるだけなので、継承すると元に戻ります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private import std.stdio: writeln;

class Test {
    void print() {
        writeln("CTest.print");
    }
}

static class Test_hook {
    private static void* old_print;
    
    static this() {
        old_print = cast(void*)&Test.print;
        
        foreach(ref fp; Test.classinfo.vtbl) {
            if(fp == old_print) {
                fp = cast(void*)&typeof(this).print;
            }
        }
    }
    
    private void print() {
        writeln("CTest_hook.print before");
        (cast(void function(void*))old_print)(cast(void*)this);
        writeln("CTest_hook.print after");
    }
    
}

void main() {
    auto o = new Test();
    o.print;
}

PostScript です。
なんか、もっと簡単に書く方法がありそうな。
TestFunction, EnterFunction, LeaveFunction が定義されているとして、
/TestFunction (Label) /EnterFunction /LeaveFunction AddHook
のようにして使用します。(ネスト可)
外すときには
/TestFunction RemoveHook 
で、TestFunction に関して最後に指定したものから1段階外れます。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
%!PS

/HookExec { % {Function} (Memo) {EnterFunction} {LeaveFunction}  HookExec  -
    2 index 3 -1 roll cvx exec
    currentdict /HookFunctions-temp known not
    {
        /HookFunctions-temp [] def
    } if
    HookFunctions-temp aload length dup
    3 add -2 roll
    3 -1 roll 2 add array astore
    /HookFunctions-temp exch def

    cvx exec
    
    HookFunctions-temp aload length 2 sub 3 1 roll cvx
    exec

    array astore
    /HookFunctions-temp exch def
} bind def


/AddHook { % {Function} (Memo) {EnterFunction} {LeaveFunction}  AddHook  -
    currentdict 4 index known
    {
        currentdict 4 index get
        4 1 roll
        currentdict exch 2 copy known {
            get cvx
        } {
            cvx exch pop
        } ifelse
        exch
        currentdict exch 2 copy known {
            get cvx
        } {
            cvx exch pop
        } ifelse
        exch

        /HookExec cvx
        % /TestLoop2 {Func} (Memo) {Enter} {Leave}
        5 array astore
        % /TestLoop2 [  ]
        cvx currentdict 3 1 roll put
    } {
        (Can't hook ) print pop pop print ( / ) print =
    } ifelse
} bind def


/RemoveHook { % /Function Name RemoveHook
    dup currentdict exch known
    {
        dup currentdict exch get
        dup length 5 eq {
            dup 4 get /HookExec eq {
                0 get 
                def
            } {
                (Remove Hook: Invalid data ) print pop =
            } ifelse
        } {
            (Remove Hook : Ignored ) print pop =
        } ifelse
    } {
        (Remove Hook : Unknown Operator) print =
    } ifelse
} bind def

% ----------------- Test Code ----------------
/EnterHook { % (Label) EnterHook -  
    (Enter: ) print dup =
    currentdict /ProfilingTimer known not {
        /ProfilingTimer  10 dict def
    } if
    ProfilingTimer exch 2 copy known {
        get
    } {
        [0 0] dup 4 1 roll put
    } ifelse
    1 usertime 1000 div put 
} bind def

/LeaveHook { % (Label) LeaveHook -  
    (Leave: ) print dup print ( : ) print
    
    currentdict /ProfilingTimer known {
        ProfilingTimer exch 2 copy known
        {
            get
            aload 3 1 roll
            usertime 1000 div sub neg
            dup 10 string cvs print ( sec  Total =) print
            add
            dup 10 string cvs print ( sec) =
            0 exch put
        } {
            pop pop
        } ifelse
    } {
        pop
    } ifelse
} bind def

/TestLoop2 { % Count  TestLoop2 -
    10000 mul {
        1000 {
        } repeat
    } repeat
} def 

/TestLoop { % - TestLoop -
    0 1 5 {
        TestLoop2
    } for
} def

/TestLoop2 (Loop2) /EnterHook /LeaveHook AddHook 
% 2nd level hook
/TestLoop2 (Loop2) {(Enter ) print =} {(Leave ) print =} AddHook

/TestLoop (Loop) /EnterHook /LeaveHook AddHook 

TestLoop

%Remove top level hook

/TestLoop2 RemoveHook

TestLoop