委托在.Net里面被托管代碼封裝了之后,看起來似乎有些復(fù)雜。但是實(shí)際上委托即是函數(shù)指針,而多播委托,即是函數(shù)指針鏈。本篇來只涉及底層的邏輯,慎入。
概括1.示例代碼
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+=的等號(hào)后面每放一個(gè)函數(shù),就相當(dāng)于多了一個(gè)函數(shù)指針。號(hào)稱:多播委托。
【資料圖】
2.多播原理偽代碼以上委托可以簡化成以下偽代碼,其它所有多播委托均可依次類推。
int i;// i表示多播委托的次數(shù)if(i==1) //也就是只test.AAA += new ABC(test.A);然后調(diào)用test.AAA(){ test.A() //只有一個(gè)多播,直接調(diào)用這一個(gè)函數(shù)}else // 如果大于一個(gè)多播委托,如示例兩個(gè)多播{ IntPtr FunPtr=test.A()+test.B(); //函數(shù)A和函數(shù)B形成了一個(gè)新的托管地址 FunPtr();//在新形成的托管地址里面分別調(diào)用函數(shù)A和函數(shù)B}
3.內(nèi)存模型對(duì)象(object)的內(nèi)存,大致是:
為了簡潔,實(shí)質(zhì)非常龐大header+MethodTable+field
委托根據(jù)對(duì)象來,以示例代碼的test對(duì)象為例,test對(duì)象有一個(gè)filed也即是委托類型的變量AAA。AAA則是new ABC得來的。new ABC所實(shí)例化對(duì)象的filed是分別為函數(shù)A,B。那么他們的內(nèi)存模型如下所示:
test==header+Mehtodtalbe + AAA(test.AAA(1) or test.AAA(2)+test.AAA(1))test.AAA(1)==new ABC(test.A):header+Methodtable+函數(shù)A(precode)test.AAA(2)==new ABC(test.B):header+Methodtable+函數(shù)B(precode)
特例:當(dāng)只有一個(gè)多播委托(多播偽代碼里的i==1),類似于以下這種情況:
如果:static void Main(string[] args){ Test test = new Test(); test.AAA += new ABC(test.A);//只有一個(gè)多播 test.AAA(); //test.AAA.Invoke();}那么:test==header+Mehtodtalbe + AAA(test.AAA(1))test.AAA(1)==new ABC(test.A)(header+Methodtable+函數(shù)A(precode,offset:0x18))內(nèi)存:0x000001DB38D552C0 00007ffa3b3654d8 000001db38d55858這里的0x000001DB38D552C0即test的MethodTable地址。000001db38d55858即new ABC(test.A)的MethodTable地址
委托里面只有一個(gè)方法test.A(多播偽代碼里的i==1),這種情況的話,JIT會(huì)直接尋找test.AAA(1)的MethodTable,加上偏移位0x18,也即是函數(shù)test.A的函數(shù)地址。然后運(yùn)行。
注意了,因?yàn)閷?duì)象test只有一個(gè)filed:AAA。超過一個(gè)以上的多播(多播偽代碼里的i!=1,也即else邏輯),它的field是一直變化的,比如new ABC(test.A)的時(shí)候,它的filed是test.AAA(1)。而new ABC(test.B)的時(shí)候,它的field則是test.AAA(2)+test.AAA(1)組合成的托管函數(shù),覆蓋掉前面的。如果有test.AAA(3),那么后面繼續(xù)組合,繼續(xù)覆蓋test對(duì)象的field。
當(dāng)它組合之后,形成一個(gè)新的地址,CLR會(huì)在這個(gè)地址的基礎(chǔ)上加上偏移量0x18(同上特例)進(jìn)行托管函數(shù)代碼調(diào)用。JIT Compile之后,在里面分別調(diào)用函數(shù)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
4.托管和非托管依次調(diào)用順序,以下函數(shù)按照順序在多播委托中調(diào)用:托管:
System.MulticastDelegate:CtorClosed //把對(duì)象test對(duì)象的field設(shè)置為abcSystem.Delegate:Combine //組合成新的委托,也即函數(shù)指針鏈,如果只有一個(gè)多播,則即那一個(gè)函數(shù)指針System.Runtime.CompilerServices.CastHelpers.ChkCastClass //進(jìn)行類型轉(zhuǎn)換
非托管:
JIT_WriteBarrier //設(shè)置card_table,防止GC標(biāo)記的時(shí)候漏掉
5.原理圖多播委托原理如下圖所示:單個(gè)委托實(shí)際上就是調(diào)用函數(shù)指針,而多個(gè)委托,則是通過多播委托組合單個(gè)委托形成一個(gè)新的托管函數(shù),在這個(gè)托管函數(shù)里面進(jìn)行單個(gè)函數(shù)一一調(diào)用。
結(jié)尾作者:江湖評(píng)談關(guān)注公眾號(hào):jianghupt。后臺(tái)回復(fù):dotnet7。獲取一套.Net7 CLR源碼教程。
關(guān)鍵詞: