メソッドのフック
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.
see: Proxy (Java 2 Platform SE 5.0)
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
see: goo辞書
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]
|
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 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;
}
}
}
|
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++で関数テンプレートです。 方法は、スレッドにすでに投稿されてる方々の方法を参考にしています。 実際本気でやろうと思ったら、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;
}
|
参考ページを貼り損ねてしまった・・・
see: 初級C言語Q&A
アスペクト指向を 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()
|
関数の戻り値を返せるようにするには、グローバル変数を使うのが簡単そうです。
または、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}){}
}
|
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();
})();
|
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
|





todogzm
#6017()
Rating0/8=0.00
例えば、あるクラスのあるメソッドを実行する前に他の処理を呼びたい(例えばログやトランザクション開始など)。 また、そのメソッドの終了後にも何らかの後処理を呼びたい(トランザクション終了など)。
そのような、メソッドに対するフック処理を書いてください。 ライブラリを使用してメソッドのフックを実現した場合は ライブラリの名前を紹介してくれると助かります。
[ reply ]