MINGW - 正确运行函数所需的 cdecl

mingw - cdecl required to run functions correctly

本文关键字:cdecl 函数 运行 MINGW      更新时间:2023-10-16

下面的代码在我找到的任何在线 gcc 编译器(gcc 9.2.0(中都能正常运行,它也在 CYGWIN gcc 编译器中正常运行,但不幸的是它不能很好地与 MINGW gcc 编译器配合使用 - 看起来它将无效参数作为"this"传递给"methodA"和"methodB"方法,当它们被调用时,而不是预期的结果 (56,58(,我得到了一些随机的高数字。

#include <iostream>
using namespace std;
class CallbackBase
{
public:
using METHOD_TYPE = int  (CallbackBase::*)(...);
};  
class CallbackProvider : public CallbackBase
{
public:
int methodA(int a,int b,int c)
{
return a+b+c+d;
}
int methodB(double a,double b,double c)
{
return a+b+c+d;
}
private:
int d=8;
};
class CallbackRunner
{
public:
CallbackBase::METHOD_TYPE m_method;
CallbackBase* m_this;
void install (CallbackBase* _this, CallbackBase::METHOD_TYPE _method)
{
m_method =_method;
m_this =_this;
}
int Run1()
{
return (m_this->*m_method)(15L,16L,17L);
}
int Run2()
{
return (m_this->*m_method)(15.6,16.7,17.8);
}
};
int main()
{
CallbackProvider    cp;
CallbackRunner      cr;
cr.install(&cp,(CallbackBase::METHOD_TYPE)&CallbackProvider::methodA);
cout << "result " << cr.Run1() << endl;
cr.install(&cp,(CallbackBase::METHOD_TYPE)&CallbackProvider::methodB);
cout << "result " << cr.Run2() << endl;
return 0;
}

如果我__cdecl属性添加到此方法中,问题就解决了:

int __cdecl methodA(int a,int b,int c)
int __cdecl methodB(double a,double b,double c) 

我不使用-mrtd编译标志。 据此,__cdecl应该是 gcc 编译器的默认调用约定,但看起来 MINGW 并非如此。

是否可以将__cdecl设置为项目的默认调用约定? 或者作为替代方案,有没有办法为所有方法设置"默认"属性?

我使用的是具有 10 位架构的 Windows 64。

不允许使用带有 C 样式可变参数的函数指针来调用具有常规参数的函数。如果没有指针强制转换,您的cr.install调用将无法正常工作是有原因的。

最简单的解决方案是在调用回调指针之前,根据要用来调用它的参数,将回调指针强制转换为正确的类型。您可以编写一个模板来为您进行演员转换。

但是,这是非常不安全的,因为它很容易转换为不正确的类型。(你的代码同样不安全,如果一开始它不是未定义的。

更安全的方法是将函数指针存储在std::any中。然后,尝试使用无效参数进行回调调用将导致运行时错误(即,std::any_cast将检测参数类型mistmatch(。

你的代码有一个错误 - 它有未定义的行为。从int (Class::*)(int, int, int)int (CallbackBase::*)(...)的演员阵容会触发它。这两种类型是不一样的,你不能随意地在它们之间投射。

这是您尝试此非法转换的代码的摘录:

cr.install(&cp,(CallbackBase::METHOD_TYPE)&CallbackProvider::methodA);

如果移除强制转换,您可以自己轻松看到诊断消息:

error: 無法將 'int (CallbackProvider::*((int

, int, int(' 轉換 'CallbackBase::METHOD_TYPE' {aka 'int (CallbackBase::*((...('}

它适用于某些编译器而不是其他编译器这一事实没有任何意义,它只是未定义行为的表现。

您可以在调用它之前转换回原始函数类型,但整个事情会变得更加丑陋。

另一种选择可能是使您的具体函数也可变参数,并通过VA_ARGS访问参数。这会把整个C++类型的安全从窗户上扔掉,我也不喜欢这种方法。

只是为了琐事,看起来 mingw 的 gcc 对可变参数函数使用传统的调用约定(cdecl确实很传统(,而其他编译器将使用现代 AMD ABI。