点击上方蓝字 江湖评谈设为关注
前言
本篇承接上一篇:.NET9 FCall/QCall调用约定,继续源码级分析下F,C级的调用过程。
QCall
QCall在CLR里面会把函数名和函数地址放入到数组,比如Buffer_MemMove
#define DllImportEntry(impl) \
{#impl, (void*)impl},
static const Entry s_QCall[] =
{
//省略部分代码,便于观察
DllImportEntry(Buffer_MemMove)
}
Entry的定义:
typedef struct
{
const char* name; //函数名
const void* method;//函数地址
} Entry;
实际上s_QCall数组宏定义展开的就是包含了函数名,函数地址的数组。
当C#有如下代码:
[DllImport("QCall", CharSet = CharSet.Unicode)]
private unsafe static extern void Buffer_MemMove(byte* dest, byte* src, [NativeInteger] UIntPtr len);
CLR首先会查找s_QCall数组里名称为Buffer_MemMove的函数,然后返回这个函数的地址。接着跳转到这个函数地址,运行Buffer_MemMove。
这里的跳转函数地址,以及查找函数名是通过CLR来运行的。但是在此之前是托管的C#,切换到托管C#到CLR的正是NDirectImportThunk汇编函数。
NESTED_ENTRY NDirectImportThunk, _TEXT
;
; Allocate space for XMM parameter registers and callee scratch area.
;
alloc_stack 68h
;
; Save integer parameter registers.
; Make sure to preserve r11 as well as it is used to pass the stack argument size from JIT
;
save_reg_postrsp rcx, 70h
save_reg_postrsp rdx, 78h
save_reg_postrsp r8, 80h
save_reg_postrsp r9, 88h
save_reg_postrsp r11, 60h
save_xmm128_postrsp xmm0, 20h
save_xmm128_postrsp xmm1, 30h
save_xmm128_postrsp xmm2, 40h
save_xmm128_postrsp xmm3, 50h
END_PROLOGUE
;
; Call NDirectImportWorker w/ the NDirectMethodDesc*
;
mov rcx, METHODDESC_REGISTER
call NDirectImportWorker
;
; Restore parameter registers
;
mov rcx, [rsp + 70h]
mov rdx, [rsp + 78h]
mov r8, [rsp + 80h]
mov r9, [rsp + 88h]
mov r11, [rsp + 60h]
movdqa xmm0, [rsp + 20h]
movdqa xmm1, [rsp + 30h]
movdqa xmm2, [rsp + 40h]
movdqa xmm3, [rsp + 50h]
;
; epilogue, rax contains the native target address
;
add rsp, 68h
TAILJMP_RAX
NESTED_END NDirectImportThunk, _TEXT
FCall
相对于QCall,FCall则简单许多了,它在JIT编译的时候就已经确定好了函数头位置(函数地址),在调用的时候直接跳转即可。
如下C#代码:
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern int _CollectionCount(int generation, int getSpecialGCCount);
当调用GC.CollectionCount(0)的时候,托管首先跳转到
src\coreclr\System.Private.CoreLib\src\System\GC.CorrCLR.cs
public static int CollectionCount(int generation)
{
ArgumentOutOfRangeException.ThrowIfNegative(generation);
return _CollectionCount(generation, 0);
}
这里的_CollectionCount会直接跳转到FCIMPL函数,关于FCIMPL可以参考上一篇文章.NET9 FCall/QCall调用约定。下面是它跳转的代码:
FCIMPL2(int, GCInterface::CollectionCount, INT32 generation, INT32 getSpecialGCCount)
{
FCALL_CONTRACT;
//We've already checked this in GC.cs, so we'll just assert it here.
_ASSERTE(generation >= 0);
//We don't need to check the top end because the GC will take care of that.
int result = (INT32)GCHeapUtilities::GetGCHeap()->CollectionCount(generation, getSpecialGCCount);
FC_GC_POLL_RET();
return result;
}
FCIMPLEND
以上就是F,C大致调用过程了。了解更多,或者想要学习.NET9顶级技术,可以加入CLR/JIT学习圈。
往期精彩回顾