在Windows上是__cdecl还是__stdcall?


84

我目前正在为Windows开发一个C ++库,该库将作为DLL分发。我的目标是最大程度地实现二进制互操作性。更确切地说,我的DLL中的功能必须可以从使用多个版本的MSVC ++和MinGW编译的代码中使用,而不必重新编译DLL。但是,我对哪种调用约定最好cdecl还是感到困惑stdcall

有时我听到诸如“ C调用约定是唯一保证在整个编译器中都相同的声明”之类的语句,这与诸如“的解释存在某些变化cdecl,尤其是如何返回值”之类的语句形成对比。这似乎并未阻止某些库开发人员(如libsndfile)在其分发的DLL中使用C调用约定,而没有任何可见的问题。

另一方面,stdcall调用约定似乎定义明确。据我了解,基本上所有Windows编译器都必须遵循它,因为它是Win32和COM的约定。这是基于这样的假设:没有Win32 / COM支持的Windows编译器将不是很有用。论坛上发布的许多代码片段都声明了函数的功能,stdcall但我似乎找不到一个明确解释原因的帖子。

那里有太多相互矛盾的信息,我进行的每次搜索都会给我不同的答案,但这并不能真正帮助我在两者之间做出决定。我正在寻找一个清晰,详细,有争议的解释,以说明为什么我应该选择一个而不是另一个(或者为什么两者相等)。

请注意,这个问题不仅适用于“经典”的功能,而且还虚成员函数调用,因为大多数客户端代码将与我的DLL通过“接口”,纯虚类接口(以下模式,例如描述在这里那里)。


4
“ cdecl的解释中存在一些变化,特别是在如何返回值方面”-这大约意味着在x86上使用cdecl的不同OS可能会使用cdecl的不同变体,即它们都称为“ cdecl”的不同ABI。Windows限制了这些变化,任何在Windows上运行且不尊重Windows选择的实现都将无法调用Windows cdecl函数,因此正如您使用stdcall所说的那样,它对于Windows编程是无用的,并且有一些标准C功能将很难实现。
史蒂夫·杰索普

1
__stdcall调用约定用于调用Win32 API函数。docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2017
Zanna

Answers:


79

我只是进行了一些实际测试(使用MSVC ++和MinGW编译DLL和应用程序,然后将它们混合使用)。看起来,cdecl调用约定对我有更好的效果。

更具体地说:问题stdcall在于,即使使用,MSVC ++也会破坏DLL导出表中的名称extern "C"。例如foo变为_foo@4。这仅在使用__declspec(dllexport)时发生,而不是在使用DEF文件时发生;但是,我认为DEF文件很麻烦,因此我不想使用它们。

MSVC ++名称处理带来两个问题:

  • GetProcAddress在DLL上使用变得稍微复杂一些;
  • 默认情况下,MinGW不会在修饰的名称前加反斜线(例如,MinGW将使用foo@4而不是_foo@4),这会使链接变得复杂。同样,它也带来了看到“非下划线版本”的DLL和与“下划线版本”不兼容的应用程序泛滥的风险。

我尝试过这样的cdecl约定:MSVC ++和MinGW之间的互操作性开箱即用,并且在DLL导出表中保留未修饰的名称。它甚至适用于虚拟方法。

由于这些原因,cdecl对我来说无疑是赢家。


值得注意的是,这不适用于64位DLL,因为__stdcall和__cdecl通常都被忽略-因此在导出的C函数上不会发生名称修饰。
jtbr

@jtbr如何导出C ++函数(即no extern "C")?
Ela782 '18 -10-5

@ Ela782 C ++总是会有一些名称修饰,以支持函数重载。但这不是标准化的,因此在编译器之间可能会有所不同。
jtbr

@jtbr抱歉,我的名字不是故意的。我的意思是在带有导出的C ++函数的64位DLL中,是否也忽略__stdcall和__cdecl。
Ela782

1
@ Ela782是; 我相信__stdcall和__cdecl在所有x86-64编译中都会被忽略。参见维基百科
jtbr

20

两种调用约定的最大区别在于,在函数调用之后,“ _ _cdecl”给平衡栈带来了负担,这允许具有可变数量参数的函数。“ __stdcall”约定本质上是“简单”的,但是在这方面灵活性较差。

另外,我相信默认情况下托管语言使用stdcall约定,因此如果您使用cdecl,则在其上使用P / Invoke的任何人都必须明确声明调用约定。

因此,如果所有函数签名都将被静态定义,那么我可能会倾向于使用stdcall,如果不是cdecl的话。


11

在安全性方面,__cdecl约定是“更安全的”,因为调用者需要取消分配堆栈。__stdcall库中可能发生的事情是,开发人员可能忘记正确地释放了堆栈,或者攻击者可能通过破坏DLL的堆栈(例如,通过API挂钩)注入了一些代码,然后调用者未对其进行检查。我没有任何CVS安全示例表明我的直觉是正确的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.