委托在.Net里面被托管代码封装了之后,看起来似乎有些复杂。但是实际上委托即是函数指针,而多播委托,即是函数指针链。本篇来只涉及底层的逻辑,慎入。
示例代码
public delegate void ABC(); //委托写在类的外面
public class Test
{
public ABC AAA;
public void A() { }
public void B() { }
}
static void Main(string[] args)
{
Test test = new Test();
test.AAA += new ABC(test.A);
test.AAA += new ABC(test.B);
test.AAA(); //test.AAA.Invoke();
}
以上的test.AAA+=的等号后面每放一个函数,就相当于多了一个函数指针。号称:多播委托。
多播原理伪代码以上委托可以简化成以下伪代码,其它所有多播委托均可依次类推。
int i;// i表示多播委托的次数
if(i==1) //也就是只test.AAA += new ABC(test.A);然后调用test.AAA()
{
test.A() //只有一个多播,直接调用这一个函数
}
else // 如果大于一个多播委托,如示例两个多播
{
IntPtr FunPtr=test.A()+test.B(); //函数A和函数B形成了一个新的托管地址
FunPtr();//在新形成的托管地址里面分别调用函数A和函数B
}
内存模型对象的内存,大致是:
为了简洁,实质非常庞大
header+MethodTable+field
委托根据对象来,以示例代码的test对象为例,test对象有一个filed也即是委托类型的变量AAA。AAA则是newABC得来的。newABC所实例化对象的filed是分别为函数A,B。那么他们的内存模型如下所示:
test==header+Mehtodtalbe + AAA(test.AAA(1) or test.AAA(2)+test.AAA(1))
test.AAA(1)==new ABC(test.A):header+Methodtable+函数A(precode)
test.AAA(2)==new ABC(test.B):header+Methodtable+函数B(precode)
特例:当只有一个多播委托,类似于以下这种情况:
如果:
static void Main(string[] args)
{
Test test = new Test();
test.AAA += new ABC(test.A);//只有一个多播
test.AAA(); //test.AAA.Invoke();
}
那么:
test==header+Mehtodtalbe + AAA(test.AAA(1))
test.AAA(1)==new ABC(test.A)(header+Methodtable+函数A(precode,offset:0x18))
内存:
0x000001DB38D552C0 00007ffa3b3654d8 000001db38d55858
这里的0x000001DB38D552C0即test的MethodTable地址。
000001db38d55858即new ABC(test.A)的MethodTable地址
委托里面只有一个方法test.A,这种情况的话,JIT会直接寻找test.AAA的MethodTable,加上偏移位0x18,也即是函数test.A的函数地址。然后运行。
注意了,因为对象test只有一个filed:AAA。超过一个以上的多播,它的field是一直变化的,比如newABC的时候,它的filed是test.AAA。而newABC的时候,它的field则是test.AAA+test.AAA组合成的托管函数,覆盖掉前面的。如果有test.AAA,那么后面继续组合,继续覆盖test对象的field。
当它组合之后,形成一个新的地址,CLR会在这个地址的基础上加上偏移量0x18进行托管函数代码调用。JITCompile之后,在里面分别调用函数test.A,test.B,完成委托的多播。参照如下代码:
test.AAA(); //test.AAA.Invoke();
00007FFA3AFF7A27 mov rcx,qword ptr [rbp+28h]
00007FFA3AFF7A2B mov rcx,qword ptr [rcx+8]
00007FFA3AFF7A2F mov rax,qword ptr [rbp+28h]
00007FFA3AFF7A33 call qword ptr [rax+18h]
00007FFA3AFF7A36 nop
托管和非托管依次调用顺序,以下函数按照顺序在多播委托中调用:托管:
System.MulticastDelegate:CtorClosed //把对象test对象的field设置为abc
System.Delegate:Combine //组合成新的委托,也即函数指针链,如果只有一个多播,则即那一个函数指针
System.Runtime.CompilerServices.CastHelpers.ChkCastClass //进行类型转换
非托管:
JIT_WriteBarrier //设置card_table,防止GC标记的时候漏掉
文章为作者独立观点,不代表 股票程序化软件自动交易接口观点