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

ProxyMetaClass を利用しました。
ただし、指定したクラスの
・コンストラクタ
・メソッド
の呼び出しの両方にインターセプタが実行されるので、
対象メソッドをどれにするか列挙する必要がありますね。
 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
class MyClass{
    def MyClass(s){
        println "CONSTRUCTING"
    }
    def sayHello(name){
        println "Hello " + name
    }
}

class MyClassInterceptor implements Interceptor{
    Object beforeInvoke(Object object, String methodName, Object[] arguments){
        if( methodName == 'sayHello' ){
            println "  BEFORE"
        }
    }

    boolean doInvoke(){ true }

    Object afterInvoke(Object object, String methodName, Object[] arguments,
                     Object result){
        if( methodName == 'sayHello' ){
            println "  AFTER"
        }
        result
    }
}

def proxy= ProxyMetaClass.getInstance( MyClass )
proxy.interceptor= new MyClassInterceptor()

proxy.use{
    def invoice= new MyClass('trade')
    invoice.sayHello('GENZOU  ')
}

Forth にはメソッドはないのですが、再定義前のワードを使えるのでこんな感じでしょうか。

before
primary
after

と出力されます。再帰するわけではありません。

1
2
3
: f ." primary" cr ;
: f ." before" cr f ;
: f f ." after" cr ;

少しオーバースペックですが dynamic-wind を使うと関数への出入り(通常の呼び出し・終了、継続を使った脱出・再入)にフックをかけることができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(define (add-hook body before after)
  (lambda args
    (dynamic-wind
        before
        (lambda () (apply body args))
        after)))

(define f
  (add-hook display
            (lambda () (display "I have "))
            (lambda () (display " sisters.") (newline))))

(f 19)
;; -| I have 19 sisters.

ログの開始などだろうから、その物には影響を与えない形でしてみます。もちろん返り値には影響を与えません。利用したものはyieldを使ったブロック構文です。こうすると、メソッドチェーンの間に含めることで逐次変数の変化を見ることが出来ますね。お題のように、ログの開始終了もブロックの中に記述すればいいですね。

例)
irb(main):014:0> "1".hook{|x| p x}.to_f.hook{|x| p x}.to_i.hook{|x| p x}
"1"
1.0
1
1
1
2
3
4
def hook
  yield self
  self
end

bashやPOSIX-shでは、外部コマンドや内部コマンドよりシェル関数が優先して呼ばれます。いっぽう、commandコマンドを使うことにより、シェル関数を省いて外部コマンドや内部コマンドを呼べます。

これを組み合わせると、下のコードのようにして、外部コマンドをシェル関数でフックできます。

ただし、シェル関数はこの方法ではフックできません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ls() {
    # before
    echo '***** start *****'

    # command itself
    command ls "$@"
    local result=$?

    # after
    echo '***** end *****'

    return $result
}

.NET Frameworkには透過プロクシという機能があります。これを使えばメソッド呼出時にフックができるというわけです。このお題、C#での投稿は別の方が別の方法で存在するので、私のものはVB.NETに移植しました。なので、あまりVB.NETっぽくないと思います。DirectCastとか(Of T)なんかでは、慣れているC#のほうがいいなあと感じました。
 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
Imports System
Imports System.Runtime.Remoting.Proxies
Imports System.Runtime.Remoting.Messaging

Interface IHoge
    Sub Piyo()
End Interface

Class BeforeAfterProxy(Of T)
    Inherits RealProxy

    Public Sub New(obj As T, before As Action, after As Action)
        MyBase.New(GetType(T))
        Me.obj = obj
        Me.before = before
        Me.after = after
    End Sub

    Public Overrides Function Invoke(msg As IMessage) As IMessage
        Dim mm = DirectCast(msg, IMethodMessage)
        before()
        mm.MethodBase.Invoke(obj, mm.Args)
        after()
        Invoke = New ReturnMessage( _
            Nothing, Nothing, 0, mm.LogicalCallContext, DirectCast(msg, IMethodCallMessage))
    End Function

    private obj As T
    private before As Action
    private after As Action
End Class

Class HogeImpl
    Implements IHoge

    Sub Piyo() Implements IHoge.Piyo
        Console.WriteLine("piyo")
    End Sub

    Shared Sub Before()
        Console.WriteLine("Before")
    End Sub

    Shared Sub After()
        Console.WriteLine("After")
    End Sub

    Shared Sub Main()
        Dim hoge = New HogeImpl()
        Dim xHoge = New BeforeAfterProxy(Of IHoge)(hoge, AddressOf Before, AddressOf After)
        Dim tHoge = DirectCast(xHoge.GetTransparentProxy(), IHoge)
        tHoge.Piyo()
    End Sub
End Class

VB.NETに移す前の元のC#のコードも一応投稿しておくことにします。そういえば、VB.NETは戻り値のないラムダ式が書けないので、メソッドにするしかなかったのでした。
 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
using System;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;

interface IHoge
{
    void Piyo();
}

public class BeforeAfterProxy<T> : RealProxy
{
    public BeforeAfterProxy(T obj, Action before, Action after)
        : base(typeof(T))
    {
        this.obj = obj;
        this.before = before;
        this.after = after;
    }

    public override IMessage Invoke(IMessage msg)
    {
        var mm = (IMethodMessage)msg;
        before();
        mm.MethodBase.Invoke(obj, mm.Args);
        after();
        return new ReturnMessage(
            null, null, 0, mm.LogicalCallContext, (IMethodCallMessage)msg);
    }

    private T obj;
    private Action before;
    private Action after;
}

class HogeImpl : IHoge
{
    public void Piyo() {Console.WriteLine("piyo");}

    static void Main()
    {
        var hoge = new HogeImpl();
        var xHoge = new BeforeAfterProxy<IHoge>(hoge,
            () => Console.WriteLine("Before"),
            () => Console.WriteLine("After"));

        var tHoge = (IHoge)xHoge.GetTransparentProxy();
        tHoge.Piyo();
    }
}

Index

Feed

Other

Link

Pathtraq

loading...