点击上方蓝字 江湖评谈设为关注
前言
FCall/Qcall是托管与非托管之间的调用约定,双方需要一个契约,以弥合彼此的互相/单向调用。
非托管调用约定
先了解下非托管约定,一般有四种,分别为thiscall,stdcall ,cdecl ,fastcall
thiscall:用特定的寄存器传递当前类指针this,由编译器决定哪个寄存器传递this。自身清理堆栈,从右往左传递参数。
stdcall:一般用于win32 API函数的传递方式,自身清理堆栈,从右往左一次传参。
cdecl:一般用于微软古老的MFC框架的类的函数传递方式,调用者清理堆栈,从右往左依次传参。
fastcall :用于快速调用方式,规定前几个参数用寄存器传递,多余的参数用栈来传递。比如x64前四个参数rcx,rdx,r8,r9等。自身清理堆栈,从右往左传参。
FCall
.NET9里面需要在托管和非托管进行相互调用,如果需要调用有效,就必须双方互有约定。使托管代码与CLR保持一致。比如FCall会通过一些宏定义打乱堆栈或者寄存器里面的参数进行重新排序,再比如FCall会对返回值,参数,函数名称进行重新构造。FCall就是做这些的,下面看个例子----函数重构。
例子:
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)
//函数主体部分
FCALL_CONTRACT;
_ASSERTE(generation >= 0);
int result = (INT32)GCHeapUtilities::GetGCHeap()->CollectionCount(generation, getSpecialGCCount);
FC_GC_POLL_RET();
return result;
//FCIMPL2结尾
FCIMPL_EPILOG();
}
QCall
QCall一般使用导出标记extern,用托管匹配 CLR调用,运行出结果。调用约定遵循平台标准.
例子:把长度为len个字节从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);
}
总结
简单点来说FCall意思:调用托管函数的时候,可能会调用非托管,FCall就是从托管调用非托管的C#代码与CLR之间的约定,约定它们如何调用。
QCall的意思:QCall一般用于非托管导出(extern)的函数,在托管里面的调用。
往期精彩回顾