0

下面的代码可以在我找到的任何在线 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 设置为我的项目的默认调用约定?或者作为替代方案,有没有办法为所有方法设置“默认”属性?

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

4

2 回答 2

0

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

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

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

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

于 2020-06-19T20:07:52.207 回答
0

您的代码有一个错误 - 它具有未定义的行为。int (Class::*)(int, int, int) 从to的演员表int (CallbackBase::*)(...)触发它。这两种类型不一样,你不能在它们之间随意转换。

这是您尝试进行这种非法强制转换的代码的摘录:

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

如果您删除演员表,您可以自己轻松地看到诊断消息:

错误:无法将 'int (CallbackProvider::*)(int, int, int)' 转换为 'CallbackBase::METHOD_TYPE' {aka 'int (CallbackBase::*)(...)'}

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

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

另一种选择可能是使您的具体函数也可变参数,并通过VA_ARGS. 这会将整个 C++ 类型安全性抛诸脑后,我也不喜欢这种方法。

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

于 2020-06-19T19:59:46.233 回答