聊聊 .NET9 FCall/QCall 调用约定
创始人
2025-07-14 05:01:29
0

前言

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)的函数,在托管里面的调用。

相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...
规避非法攻击 用好路由器远程管... 单位在市区不同位置设立了科技服务点,每一个服务点的员工都通过宽带路由器进行共享上网,和单位网络保持联...
范例解读VB.NET获取环境变... VB.NET编程语言的使用范围非常广泛,可以帮助开发人员处理各种程序中的需求,而且还能对移动设备进行...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...