聊聊 .NET9 FCall/QCall 调用约定

发表于:2024-3-12 09:19

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:江湖评谈    来源:江湖评谈

  前言
  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)的函数,在托管里面的调用。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号