0

The code below is running correctly with any online gcc compiler I found (gcc 9.2.0), it also run correctly with CYGWIN gcc compiler, but unfortunately it doesn't work well with MINGW gcc compiler - looks like it passes invalid parameter as "this" to "methodA" and "methodB" methods, when they are called, instead of expected results (56,58) i get some random high numbers.

#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;
}

The problem is solved if I add __cdecl attribute to this methods:

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

I doesn't use -mrtd compilation flag. According to this, __cdecl should be a default calling convention for gcc compilers but looks like it doesn't the case for MINGW.

Is that possible to set __cdecl as a default calling convention for my project? or as alternative, is there a way to set "default" attribute to all the methods?

I am using Windows 10 with 64 bit architecture.

2 Answers2

0

Your code has a bug - it has undefined behavior. The cast from int (Class::*)(int, int, int) to int (CallbackBase::*)(...) triggers it. Those two types are not the same, and you can not cast between them willy-nilly.

This is an extract of your code where you are attempting this illegal cast:

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

You can easily see the diagnostic message yourself if you remove the cast:

error: cannot convert 'int (CallbackProvider::*)(int, int, int)' to 'CallbackBase::METHOD_TYPE' {aka 'int (CallbackBase::*)(...)'}

The fact that it works on some compilers and not on the others is of no significance, it is just a manifestation of undefined behavior.

You could cast back to the original function type before calling it, but than the whole thing would become even more ugly.

Another option might be to make your concrete functions variadic as well, and access parameters via VA_ARGS. This would throw the whole C++ type safety off the window, and I do not like this approach either.

Just for the trivia, it looks like mingw's gcc uses a legacy calling convention (cdecl is indeed quite legacy) for variadic functions, while other compilers would use modern AMD ABI.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
SergeyA
  • 61,605
  • 5
  • 78
  • 137
0

You're not allowed to use a pointer to function with a C-style variadic parameter to call functions with regular parameters. There's a reason your cr.install calls don't work without a pointer cast.

The simplest solution is to cast the callback pointer to the proper type before calling it, based on the arguments you want to call it with. You can write a template that will do the cast for you.

However, this is highly unsafe, since it's easy to cast to an incorrect type. (Your code would be equally unsafe, if it wasn't undefined to begin with.)

A safer approach is to store the function pointer in a std::any. Then, attempting a callback call with invalid parameters will cause a runtime error (that is, std::any_cast will detect parameter type mistmatch).

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207