__cdecl or __stdcall on Windows?

__cdecl or __stdcall on Windows?

本文关键字:Windows on stdcall cdecl or      更新时间:2023-10-16

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

有时我会听到这样的陈述:;C调用约定是唯一一个保证是相同的"交叉编译器";,这与诸如";cdecl的解释有一些变化,特别是在如何返回值"方面;。这似乎并没有阻止某些库开发人员(如libsndfile)在他们分发的DLL中使用C调用约定,而不会出现任何明显的问题。

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

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

注意,这个问题不仅适用于";经典的";函数,但也可以调用虚拟成员函数,因为大多数客户端代码将通过"与我的DLL接口;接口";,纯虚拟类(以下模式描述,例如这里和那里)。

我刚刚做了一些真实世界的测试(使用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显然是我的赢家。

这两种调用约定的最大区别在于,"__cdecl"在函数调用后将平衡堆栈的负担放在了调用者身上,这允许函数具有可变数量的参数。"__stdcall"约定本质上"更简单",但在这方面灵活性较差。

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

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

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