|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
你觉得学习.NET怎么样,我懂的少,问的可能很幼稚,见笑了啊:)我原本觉得托付很复杂,原本只想简复杂单的说说托付面前的器材,托付的利用办法。底本只想注释一下那句:托付是面向工具的、范例平安的函数指针。可没想到最初惹出一堆的事变来,越惹越多,罪恶,罪恶。本文前面一部分是我在一边用SOS探究一边纪录的,写的十分糟,但愿您的慧眼能发明一些有代价的器材,那我就感应非常的侥幸了。托付宿世与此生
人人大概还记得,在C/C++里,我们能够在一个函数里完成一个算法的骨架,然后在这个函数的参数里放一个“钩子”,利用的时分,使用这个“钩子”注进一个函数,注进的函数完成分歧算法的分歧部分,如许就能够到达算法骨架重用的目标。而这里所谓的“钩子”就是“函数指针”。这个功效很壮大啊,可是函数指针却有它的优势:不是范例平安的、只能“钩”一个函数。人人大概都晓得微软对托付的形貌:托付是一种面向工具的,范例平安的,能够多播的函数指针。要了解这句话,我们先来看看用C#的关头字delegate声明的一个托付究竟是甚么样的器材:- 1:namespaceYuyijq.DotNet.Chapter2
复制代码- 3:publicdelegatevoidMyDelegate(intpara);
复制代码 埋没在面前的奥密
很复杂的代码吧,利用ILDasm反编译一下:
奇异的是,这么复杂的一行代码,酿成了一个类:类名与托付名分歧,这个类承继自System.MulticastDelegate类,连机关器一同有四个成员。看看我们怎样利用这个托付:- 1:publicclassTestDelegate
复制代码- 5:publicvoidAssignDelegate()
复制代码 编译后用ILDasm看看了局:
.fieldprivateclassYuyijq.DotNet.Chapter2.MyDelegatemyDelegate
发明,.Net把托付就当作一个范例,与其他范例一样看待,如今你分明了下面那句话中说托付是面向工具的函数指针的意义了吧。
接着看看AssignDelegate反编译后的代码:- 3:publicdelegatevoidMyDelegate(intpara);0
复制代码- 3:publicdelegatevoidMyDelegate(intpara);1
复制代码- 3:publicdelegatevoidMyDelegate(intpara);2
复制代码- 3:publicdelegatevoidMyDelegate(intpara);3
复制代码- 3:publicdelegatevoidMyDelegate(intpara);4
复制代码- 3:publicdelegatevoidMyDelegate(intpara);5
复制代码- 3:publicdelegatevoidMyDelegate(intpara);6
复制代码- 3:publicdelegatevoidMyDelegate(intpara);7
复制代码- 3:publicdelegatevoidMyDelegate(intpara);8
复制代码- 3:publicdelegatevoidMyDelegate(intpara);9
复制代码 经由过程下面的代码,我们会发明,将一个实例办法分派给托付时,托付不单单援用了办法的地点,另有这个办法地点工具的援用,这里就是所谓的范例平安。
我们再回过火来看看MyDelegate的承继链:MyDelegate->MulticastDelegate->Delegate。
奇奥的中央
而Delegate中有三个风趣的字段:
internalobject_target;
internalIntPtr_methodPtr;
internalIntPtr_methodPtrAux;
对这三个字段做具体申明
_target
1、假如托付指向的办法是实例办法,则_target的值是指向方针办法地点工具的指针
2、假如托付指向的是静态办法,则_target的值是托付实例本身
_methodPtr
1、假如托付指向的办法是实例办法,则_methodPtr的值指向一个JITStub(假如这个办法还没有被JIT编译,关于JITStub会在前面的章节先容),或指向该办法JIT后的地点
2、假如托付指向的办法是静态办法,则_methodPtr指向的是一个Stub(一段小代码,这段代码的感化是_target,然后挪用_methodPtrAux指向的办法),并且一切署名不异的托付,共享这个Stub。为何要如许一个Stub?我想是为了让经由过程托付挪用办法的流程一致吧,不论指向的是实例办法仍是静态办法,关于内部来讲,只必要挪用_methodPtr指向的地点,可是关于挪用实例办法而言,它必要this,也就是这里的_target,而静态办法不必要,为了让这里的历程一向,CLR会悄悄的在托付指向静态办法时拔出一小段代码,用于往失落_target,而间接jmp到_methodPtrAux指向的办法。
_methodPtrAux
1、假如托付指向的是实例办法,则_methodPtrAux就是0。
2、假如托付指向的是静态办法,则这时候_methodPtrAux起的感化与_mthodPtr在托付指向实例办法的时分是一样的。
实践上经由过程反编译Delegate的代码发明,Delegate有一个只读属性Target,该Target的完成依托GetTarget办法,该办法的代码以下:实了当托付指向静态办法时,Target属性为null。
我们来本人下手,剖析一下下面的结论是不是准确。
_target和_methodPtr真的如下面所说的么?何不本人下手看看。
创建一个Console范例的工程,在项目属性的“调试(Debug)”选项卡里选中“同意非托管代码调试(Enableunmanagedcodedebuging)”。- 1:namespaceYuyijq.DotNet.Chapter2
复制代码- 3:publicdelegatevoidMyDelegate(intpara);
复制代码- 1:publicclassTestDelegate4
复制代码- 1:publicclassTestDelegate5
复制代码- 1:publicclassTestDelegate6
复制代码- 1:publicclassTestDelegate7
复制代码- 1:publicclassTestDelegate8
复制代码- 1:publicclassTestDelegate9
复制代码 下面是作为实行的代码。
在CallByDelegate办法的第二行设置断点
F5实行,射中断电后,在VisualStudio的当即窗口(ImmediateWindow)里输出以下命令(菜单栏->调试(Debug)->当即窗口(Immediate)):
//.loadsos.dll用于加载SOS.dll扩大
.loadsos.dll
extensionC:WindowsMicrosoft.NETFrameworkv2.0.50727sos.dllloaded
//DumpStackObjects的缩写,输入栈中的一切工具
//该命令的输入有三列,第二列Object就是该工具在内存中的地点
!dso
PDBsymbolformscorwks.dllnotloaded
OSThreadId:0x1588(5512)
ESP/REGObjectName
0037ec10019928a4Yuyijq.DotNet.Chapter2.TestDelegate
0037ed50019928a4Yuyijq.DotNet.Chapter2.TestDelegate
0037ed5c019928b0Yuyijq.DotNet.Chapter2.MyDelegate
0037ed60019928b0Yuyijq.DotNet.Chapter2.MyDelegate
0037ef94019928b0Yuyijq.DotNet.Chapter2.MyDelegate
0037ef98019928b0Yuyijq.DotNet.Chapter2.MyDelegate
0037ef9c019928a4Yuyijq.DotNet.Chapter2.TestDelegate
0037efe0019928a4Yuyijq.DotNet.Chapter2.TestDelegate
0037efe4019928a4Yuyijq.DotNet.Chapter2.TestDelegate
//do命令为DumpObjects缩写,参数为工具地点,输入该工具的一些信息
!do019928b0
Name:Yuyijq.DotNet.Chapter2.MyDelegate
MethodTable:00263100
EEClass:002617e8
Size:32(0x20)bytes
(E:StudyDemoDemoinDebugDemo.exe)
//该工具的一些字段
Fields:
MTFieldOffsetTypeVTAttrValueName
704b84dc40000ff4System.Object0instance019928a4_target
704bd0ac40001008...ection.MethodBase0instance00000000_methodBase
704bb1884000101cSystem.IntPtr1instance0026C018_methodPtr
704bb188400010210System.IntPtr1instance00000000_methodPtrAux
704b84dc400010c14System.Object0instance00000000_invocationList
704bb188400010d18System.IntPtr1instance00000000_invocationCount
在最初Fields一部分,我们看到了_target喝_methodPtr,_target的值为019928a4,看看下面!dso命令的输入,这个不就是Yuyijq.DotNet.Chapter2.TestDelegate实例的内存地点么。
在下面的!do命令的输入中,我们看到了MethodTable:00263100,这就是该工具的办法表地点(关于办法表更具体的会商会在前面的章节先容到,如今你只需把他看作一个纪录工具一切办法的列表就好了,该列内外每个条目就是一个办法)。如今我们要看看Yuyijq.DotNet.Chapter2.TestDelegate..Test办法的内存地点,看起是不是与_methodPtr的值是分歧的,那么起首就要取得Yuyijq.DotNet.Chapter2.TestDelegate.的实例中MethodTable的值:
!do019928a4
Name:Yuyijq.DotNet.Chapter2.TestDelegate
MethodTable:00263048
EEClass:002612f8
Size:12(0xc)bytes
(E:StudyDemoDemoinDebugDemo.exe)
Fields:
None
如今晓得了其办法表的值为00263048,然后利用上面的命令找到Yuyijq.DotNet.Chapter2.TestDelegate..Test办法的地点:
!dumpmt-md00263048
EEClass:002612f8
Module:00262c5c
Name:Yuyijq.DotNet.Chapter2.TestDelegate
mdToken:02000003(E:StudyDemoDemoinDebugDemo.exe)
BaseSize:0xc
ComponentSize:0x0
NumberofIFacesinIFaceMap:0
SlotsinVTable:9
--------------------------------------
MethodDescTable
EntryMethodDescJITName
.......
0026c01000262ffcNONEYuyijq.DotNet.Chapter2.TestDelegate.AssignDelegate()
0026c0180026300cNONEYuyijq.DotNet.Chapter2.TestDelegate.Test(Int32)
......
Entry这一列就是一个JITStub。看看,公然与_methodPtr的是分歧的,由于这时候Test办法还没有经由JIT(JIT列为NONE),以是_methodPtr指向的是这里的JITStub。
假如给托付绑定一个静态办法呢?如今我们把Test办法改成静态的,那实例化托付的时分,就不克不及用this.Test了,而应当用TestDelegate.Test。仍是在原地位设置断点,利用与下面不异的命令,检察_target与_methodPtr的值。
MTFieldOffsetTypeVTAttrValueName
704b84dc40000ff4System.Object0instance01e928b0_target
704bb1884000101cSystem.IntPtr1instance007809C4_methodPtr
704bb188400010210System.IntPtr1instance0025C018_methodPtrAux
你会发明这里的_target字段的值就是MyDelegate的实例myDelegate的地点。然后我们经由过程下面的办法,找到Test办法的地点,发明_methodPtrAux的值与该值是不异的。
实践上你还能够再编写一个与MyDelegate不异署名的托付,然后也指向一个静态办法,利用不异的办法检察该托付的_methodPtr的值,你会发明这个新托付与MyDelegate的_methodPtr的值是分歧的。
方才不是说这个时分_methodPtr指向的是一个Stub么,既然云云那我们反汇编一下代码:
!u007809C4
Unmanagedcode
007809C48BC1moveax,ecx
007809C68BCAmovecx,edx
007809C883C010addeax,10h
007809CBFF20jmpdwordptr[eax]
........
.Net里JIT的办法的挪用商定是FastCall,关于FastCall来讲,办法的前两个参数会放在ECX和EDX两个存放器中。那末moveax,ecx实践上就是将_target传送给eax,再看看
704bb188400010210System.IntPtr1instance0025C018_methodPtrAux
_methodPtrAux的偏移是10,这里的addeax,10h就是将eax指向_methodPtrAux,然后jmpdwordptr[eax]就是跳转到_methodPtrAux所指向的地点了,就是托付指向的谁人静态办法。
经由过程托付挪用办法
怎样经由过程托付挪用办法呢:再来看看其对应的IL代码:- 3:publicdelegatevoidMyDelegate(intpara);3
复制代码- 5:publicvoidAssignDelegate()0
复制代码- 5:publicvoidAssignDelegate()1
复制代码 后面的代码我们已熟习,最关头的就是
callvirtinstancevoidYuyijq.DotNet.Chapter2.MyDelegate::Invoke(int32)
我们发明,经由过程托付挪用办法,实践上就是挪用托付的Invoke办法。
多播的托付
好了,既然已注释了面向工具和范例平安,那末说托付是多播的咋注释?
你大概已发明,MyDelegate承继自MulticastDelegate,看这个名字貌似有点意义了。来看看上面这两行代码:- 5:publicvoidAssignDelegate()3
复制代码- 5:publicvoidAssignDelegate()4
复制代码 经由过程IL我们能够发明,这里的+=最初就是挪用System.Delegate的Combine办法。而Combine的真正完成时在MulticastDelegate的CombineImpl办法中。在MulticastDelegate中有一个_invocationList字段,从CombineImpl中能够看出这个字段是一个object[]范例的,而托付链就放在这个数组里。
跋文
文章是想到哪儿写到哪儿,写的对照乱,也对照匆仓促。十分抱愧。关于两头那段奇奥的事变,我本来真的不晓得,我一向觉得当托付指向一个静态办法时,_target指向null就完事儿了,没想到另有这么一番情形。看来良多器材仍是不克不及想固然,亲自实验一下才晓得实在的情形。
你觉得学习.NET怎么样,我懂的少,问的可能很幼稚,见笑了啊:) |
|