聊聊 .NET9 FCall/QCall 調(diào)用約定
前言
FCall/Qcall是托管與非托管之間的調(diào)用約定,雙方需要一個契約,以彌合彼此的互相/單向調(diào)用。
非托管調(diào)用約定
先了解下非托管約定,一般有四種,分別為thiscall,stdcall ,cdecl ,fastcall
thiscall:用特定的寄存器傳遞當前類指針this,由編譯器決定哪個寄存器傳遞this。自身清理堆棧,從右往左傳遞參數(shù)。
stdcall:一般用于win32 API函數(shù)的傳遞方式,自身清理堆棧,從右往左一次傳參。
cdecl:一般用于微軟古老的MFC框架的類的函數(shù)傳遞方式,調(diào)用者清理堆棧,從右往左依次傳參。
fastcall :用于快速調(diào)用方式,規(guī)定前幾個參數(shù)用寄存器傳遞,多余的參數(shù)用棧來傳遞。比如x64前四個參數(shù)rcx,rdx,r8,r9等。自身清理堆棧,從右往左傳參。
FCall
.NET9里面需要在托管和非托管進行相互調(diào)用,如果需要調(diào)用有效,就必須雙方互有約定。使托管代碼與CLR保持一致。比如FCall會通過一些宏定義打亂堆棧或者寄存器里面的參數(shù)進行重新排序,再比如FCall會對返回值,參數(shù),函數(shù)名稱進行重新構(gòu)造。FCall就是做這些的,下面看個例子----函數(shù)重構(gòu)。
例子:
C# code: GC.CollectionCount(0);
定義:
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern int _CollectionCount(int generation, int getSpecialGCCount);
非托管:
FCIMPL2(int, GCInterface::CollectionCount, INT32 generation, INT32 getSpecialGCCount)
{
FCALL_CONTRACT;
_ASSERTE(generation >= 0);
int result = (INT32)GCHeapUtilities::GetGCHeap()->CollectionCount(generation, getSpecialGCCount);
FC_GC_POLL_RET();
return result;
}
FCIMPLEND
一般來說FCall用FCIMPL宏定義開頭,這么做的主要目的是:We align the native code shape to CoreCLR by implementing and using the and macros. These macro are responsible for using correct calling convention and shuffling the order of parameters on the stack. The macros also handle export of undecorated names using the alternatename linker/pragma trick. The downside of the trick is that linker doesn't see the comment pragma if there's no other reference to the .obj file inside a static library. There happened to be exactly two files that have only methods and no other referenced code. As a workaround I added a dummy reference from the .asm files for one function from each of those two files.FCIMPLxFCDECLxFCIMPLx。參考:https://github.com/dotnet/runtime/pull/99430
FCIMPL部分定義:
#define FCIMPL0(rettype, funcname) rettype funcname() { FCIMPL_PROLOG(funcname)
#define FCIMPL1(rettype, funcname, a1) rettype funcname(a1) { FCIMPL_PROLOG(funcname)
#define FCIMPL1_V(rettype, funcname, a1) rettype funcname(a1) { FCIMPL_PROLOG(funcname)
#define FCIMPL2(rettype, funcname, a1, a2) rettype funcname(a1, a2) { FCIMPL_PROLOG(funcname)
#define FCIMPL2VA(rettype, funcname, a1, a2) rettype funcname(a1, a2, ...) { FCIMPL_PROLOG(funcname)
下面代碼:
源碼:FCIMPL2(int, GCInterface::CollectionCount, INT32 generation, INT32 getSpecialGCCount)
宏定義:
#define FCIMPL2(rettype, funcname, a1, a2) rettype funcname(a1, a2) { FCIMPL_PROLOG(funcname)
#define FCIMPLEND FCIMPL_EPILOG(); }
展開如下:
int GCInterface::CollectionCount(int generation,INT32 getSpecialGCCount)
{
//FCIMPL2開頭
FCIMPL_PROLOG(funcname)
//函數(shù)主體部分
FCALL_CONTRACT;
_ASSERTE(generation >= 0);
int result = (INT32)GCHeapUtilities::GetGCHeap()->CollectionCount(generation, getSpecialGCCount);
FC_GC_POLL_RET();
return result;
//FCIMPL2結(jié)尾
FCIMPL_EPILOG();
}
QCall
QCall一般使用導出標記extern,用托管匹配 CLR調(diào)用,運行出結(jié)果。調(diào)用約定遵循平臺標準.
例子:把長度為len個字節(jié)從str復制到desc
[DllImport("QCall", CharSet = CharSet.Unicode)]
private unsafe static extern void Buffer_MemMove(byte* dest, byte* src, [NativeInteger] UIntPtr len);
非托管Qcall
extern "C" void QCALLTYPE Buffer_MemMove(void *dst, void *src, size_t length)
{
QCALL_CONTRACT;
memmove(dst, src, length);
}
總結(jié)
簡單點來說FCall意思:調(diào)用托管函數(shù)的時候,可能會調(diào)用非托管,F(xiàn)Call就是從托管調(diào)用非托管的C#代碼與CLR之間的約定,約定它們?nèi)绾握{(diào)用。
QCall的意思:QCall一般用于非托管導出(extern)的函數(shù),在托管里面的調(diào)用。