Windows上的__cdecl或__stdcall?

时间:2011-06-28 18:07:57

标签: c++ windows dll calling-convention binary-compatibility

我目前正在为Windows开发一个C ++库,它将作为DLL发布。我的目标是最大化二进制互操作性;更确切地说,我的DLL中的函数必须可以从使用多个版本的MSVC ++和MinGW编译的代码中使用,而无需重新编译DLL。但是,我对哪个调用约定最好,cdeclstdcall感到困惑。

有时我会听到诸如“C调用约定是唯一一个保证与编译器相同”的语句,这与“There are some variations in the interpretation of cdecl, particularly in how to return values”这样的语句形成对比。这似乎并没有阻止某些库开发人员(如libsndfile)在他们分发的DLL中使用C调用约定,而没有任何明显的问题。

另一方面,stdcall调用约定似乎是明确定义的。据我所知,所有Windows编译器基本上都需要遵循它,因为它是用于Win32和COM的约定。这是基于这样的假设:没有Win32 / COM支持的Windows编译器不会非常有用。论坛上发布的大量代码片段将函数声明为stdcall,但我似乎无法找到一篇明确解释为什么的帖子。

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

请注意,这个问题不仅适用于“经典”函数,还适用于虚拟成员函数调用,因为大多数客户端代码将通过“接口”纯粹的虚拟类与我的DLL接口(例如{{3}所描述的模式}和here)。

3 个答案:

答案 0 :(得分:67)

我刚做了一些真实的测试(用MSVC ++和MinGW编译DLL和应用程序,然后混合它们)。如图所示,我使用cdecl调用约定获得了更好的结果。

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

MSVC ++名称修改带来两个问题:

  • 在DLL上使用GetProcAddress变得稍微复杂一些;
  • 默认情况下,MinGW不会为装饰名称添加非核心(例如MinGW将使用foo@4而不是_foo@4),这会使链接变得复杂。此外,它还引入了看到“非下划线版本”的DLL和应用程序的风险,这些版本与“下划线版本”不兼容。

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

由于这些原因,cdecl对我来说是一个明显的赢家。

答案 1 :(得分:16)

两个调用约定的最大区别在于“ _ _cdecl”在调用者调用函数之后放置了平衡堆栈的负担,这允许函数具有可变数量的参数。 “__stdcall”惯例本质上是“更简单”的,但在这方面不够灵活。

另外,我认为托管语言默认使用stdcall约定,因此任何使用P / Invoke的人都必须明确说明调用约定,如果你使用cdecl。

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

答案 2 :(得分:8)

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