Show/Hide Toolbars

XSharp

 

调用约定是非托管世界的东西。它们描述了调用函数或方法时应如何传递参数,还描述了被调用函数/方法返回时由谁负责调整堆栈。

不同的编译器有不同的向函数传递参数的默认策略。

约定

描述

STRICT

这是 C/C++ 世界中最常见的调用约定。在这种约定下,参数被推入堆栈。值类型被完全推送,而对于引用类型,变量的地址被推送。当调用一个方法时,“this ”对象的地址也会被推入堆栈。

函数/方法返回后,调用方法将调整堆栈。

这种调用约定允许使用参数数量可变的函数或方法,如 printf()。调用者知道传递的参数个数,因此调用是调整堆栈的最佳选择。

在 C/C++ 中,这也被称为 __cdecl

在 VO(和 X#)中,还有一个同义词 “ASPEN”。

PASCAL

这种调用约定在 Pascal 的世界里被广泛使用。它看起来很像 STRICT 调用约定,但现在被调用的函数/方法会在返回时调整堆栈。当然,这也意味着不能有可变数量的参数。如果有必要,最后一个参数通常会变成一个数组,因此仍有一定的灵活性。

在 C/C++ 中,这被称为 __stdcall 调用约定。

Windows API 中的大多数函数都使用这种调用约定。

在 VO 中,这也被称为 WINCALL 或 CALLBACK。在 16 位的 windows 中,WINCALL 与 PASCAL 不同,但 32 位及以后的 windows 则取消了这一区别。

THISCALL

这是 PASCAL 调用约定的一个特殊变体,“this ”指针不推入堆栈,而是通过寄存器(通常是 ECX 寄存器)传递。在寄存器中传递 “this ”会更快,特别是当寄存器未被用于其他用途时,因此重复调用同一对象时不必推送 “this ”指针。在 C/C++ 中,这被称为 __thisccall

FASTCALL

这种调用方式试图在寄存器中传递尽可能多的参数。

在 C/C++ 中,这被称为 __fastcall 。

CLIPPER

这是 Xbase 世界中的一种特殊调用约定,在 Xbase 世界中,函数的参数(从技术上讲)都是可选的,而且可以传递比声明参数更多的值。最初,在 Xbase 语言中,调用代码会将值推入堆栈,同时也会传递参数数,这样被调用的函数就 “知道 ”传递了多少个参数。

在.Net 中,没有真正的等价物。为了模拟 CLIPPER 调用约定,我们生成了一个特殊的 PARAMS 参数,其中包含一个 USUAL 值数组。PARAMS 类型的参数必须是参数列表中的最后一个(或唯一一个)参数。Roslyn 编译器(我们在 x# 中使用的编译器)会自动将传递给使用 clipper 调用约定的函数/方法的所有值封装在一个数组中。

当然,当您这样声明一个函数

FUNCTION Foo(a,b,c)

然后,你希望在代码中使用 3 个名为 “a”、“b ”和 “c ”的局部变量。

然而,编译器会生成一个带有参数的函数。类似这样

FUNCTION Foo(args PARAMS USUAL[])

在函数内部,我们将根据您声明的参数名称生成局部变量:

LOCAL a := args[1] as USUAL
LOCAL b := args[2] as USUAL
LOCAL c := args[3] as USUAL

在现实中,代码会更复杂一些,因为您可能决定调用的函数参数少于声明的参数。我们必须考虑到这一点。

现在看起来是这样的

LOCAL numParams := args:Length
LOCAL a := iif(numParams > 0, args[1], NIL) AS USUAL

“numParams” 和  ”args" 的名称是由编译器生成的,其中包含一个特殊字符,以确保我们引入的变量名称不会与代码中的名称相冲突。

X# 调试器支持层也会隐藏这些特殊变量。

 

对于 “正常的” 托管代码,你实际上只需要处理两种调用约定:

对于未类型方法,编译器使用 CLIPPER 调用约定

对于类型化方法,编译器在 STRICT 和 PASCAL 之间没有区别。它们都产生相同的代码

 

只有在调用其他 DLL 中的非托管代码时,才需要使用其他调用约定。你必须 “知道” DLL 使用的是什么。一个问题是,C/C++ 代码中的调用约定往往隐藏在编译器宏中。

根据经验,C/C++ 代码应使用 STRICT,Windows api 函数应使用 PASCAL。

如果不起作用(例如,.Net 运行时抱怨堆栈问题),则应改用其他调用约定。