这个问题需要一些介绍。
我正在开发一个安全项目,它将分析CIL程序集并拒绝那些执行某些定义的“坏”事情,同时还允许托管应用程序为某些方法提供“门”,以允许某些调用被过滤。 (这是项目功能的一小部分,但这是我将在这里询问的部分。)
该项目扫描程序集中每个方法中的所有指令,并查找call,callvirt,ldftn,ldvirtftn和newobj操作码,因为这些是唯一可以最终导致方法调用的操作码。构造委托时使用ldftn操作码,如下所示:
ldarg.1
ldftn instance bool string::EndsWith(string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)
在此序列结束时,Func<string, bool>
位于堆栈的顶部。
假设我要截取对String.EndsWith(String)
的所有来电。对于call和callvirt,我可以用签名Boolean(String,String)
的静态调用替换实例调用 - 第一个参数将是最初调用该方法的字符串实例。在CIL级别上,行为将是明确且明确定义的,因为这是静态方法的调用方式。
但对于ldftn?我尝试用替换call / callvirt的操作数的相同静态方法替换ldftn指令的操作数:
ldarg.1
ldftn bool class Prototype.Program::EndsWithGate(string, string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)
我完全期望这会失败,因为委托给了一个目标对象(非null),同时传递了一个静态方法指针。令我惊讶的是,这实际上适用于Microsoft .NET运行时和Mono。我知道target / this参数只是方法的第一个参数,并且对于实例方法是隐藏的。 (该项目基于这些知识。)但代表们在这种情况下实际工作的事实对我来说有点令人费解。
所以,我的问题是:这是定义和记录的行为吗?代理在被调用时是否总是将目标压入堆栈,如果它不为空?构造一个捕获目标并“正确”调用静态方法的闭包类会更好吗,即使这会更复杂和烦人吗?
答案 0 :(得分:5)
ECMA 335规范第2部分14.6.2有一段关于此: T和D的调用约定应完全匹配,忽略静态和实例方法之间的区别。 (即如果有这个参数,则不予特别处理)。
这对我来说听起来像静态方法的内容将有两种变体:
答案 1 :(得分:3)
值得指出的是,这不是滥用行为。这是一种被称为“委托currying”的技术。它来自于一种在函数式编程语言中称为“currying”的更通用的技术,其中具有N + 1个参数的函数被转换为具有N个参数的函数.C#等价物看起来像:
Func<T2, R> CurryFirst<T1, T2, R>(
Func<T1, T2, R> f,
T1 arg
)
{
return (x) => f(arg, x);
}
Func<T1, R> CurrySecond<T1, T2, R>(
Func<T1, T2, R> f,
T2 arg
)
{
return (x) => f(x, arg);
}
CLR为“curry first”情况提供特殊支持,主要是因为在机器代码级别,curried静态方法调用看起来几乎与实例方法调用完全相同(this
参数作为隐含的第一个论点)。
这使得实现委托currying相当有效。它最初是与DynamicMethod一起实现的,以支持Iron Python。它也用于其他目的,例如允许代理人透明地引用扩展方法。
答案 2 :(得分:1)
好吧,我不认为自己会回答我的第一个问题......
#mono(Ck)的一位同事告诉我Delegate.CreateDelegate的相关行为:(强调我的)
如果提供了firstArgument,则每次调用委托时都会将其传递给方法; firstArgument被称为绑定到委托,并且该委托被称为关闭其第一个参数。 如果方法是静态的(在Visual Basic中为Shared),则在调用委托时提供的参数列表包括除第一个之外的所有参数;如果method是一个实例方法,则firstArgument将传递给隐藏的实例参数(在C#中由此表示,或由Visual Basic中的Me表示)。
从本文档中可以得出结论,在委托构造期间使用ldftn(与非空目标相结合)的这种(ab)实际上是明确定义的行为。