88

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

有时我会听到诸如“C 调用约定是唯一保证跨编译器相同的”之类的声明,这与“对 的解释存在一些变化cdecl,特别是在如何返回值方面”之类的声明形成鲜明对比。这似乎并没有阻止某些库开发人员(如libsndfile)在他们分发的 DLL 中使用 C 调用约定,而没有任何明显的问题。

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

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

请注意,这个问题不仅适用于“经典”函数,还适用于虚拟成员函数调用,因为大多数客户端代码将通过“接口”与我的 DLL 交互,纯虚拟类(遵循此处此处描述的模式)。

4

3 回答 3

81

我只是做了一些实际测试(用 MSVC++ 和 MinGW 编译 DLL 和应用程序,然后混合它们)。看起来,cdecl调用约定我得到了更好的结果。

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

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

  • 在 DLL 上使用GetProcAddress会稍微复杂一些;
  • 默认情况下,MinGW 不会在修饰名称前添加取消标记(例如,MinGW 将使用foo@4代替_foo@4),这会使链接复杂化。此外,它还引入了看到与“下划线版本”不兼容的 DLL 和应用程序的“非下划线版本”的风险。

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

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

于 2011-07-09T13:37:25.403 回答
20

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

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

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

于 2011-06-28T18:19:26.713 回答
11

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

于 2014-01-02T10:50:53.603 回答