在Windows上__cdecl或__stdcall?

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

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

另一方面, stdcall调用约定似乎是明确的。 据我所知,所有的Windows编译器基本上都需要遵循它,因为它是用于Win32和COM的惯例。 这是基于这样一个假设:没有Win32 / COM支持的Windows编译器不会非常有用。 在论坛上发布的很多代码片段声明函数为stdcall但我似乎无法find明确解释原因的单个post。

这里有太多相互冲突的信息,每次search都会给我不同的答案,这并不能帮助我在两者之间做出决定。 我正在寻找一个清晰的,详细的,辩论的解释,为什么我应该select一个在另一个(或为什么两个是相等的)。

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

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

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

MSVC ++名称mangling提出了两个问题:

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

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

由于这些原因, cdecl对我来说是一个明显的胜利者。

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

另外,我相信托管语言默认情况下使用stdcall约定,所以任何使用P / Invoke的人如果使用cdecl,就必须明确地声明调用约定。

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

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