Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

64-bit C++ passing functions with "different" calling conventions as parameters gives ambiguous error

My goal is to easily extract a prototype of an arbitrary function with both the __cdecl and __stdcall calling conventions. It works fine in 32-bit. The only thing that's changing is the calling convention in my template function parameters.

According to Wikipedia:

When compiling for the x64 architecture in a Windows context (whether using Microsoft or non-Microsoft tools), there is only one calling convention — the one described here, so that stdcall, thiscall, cdecl, fastcall, etc., are now all one and the same.

This breaks my code in 64-bit. Even though the calling conventions are the same, passing the functions as a parameter still requires that you use the correct nomenclature. I.E. if a function is defined as __stdcall, you must pass it into a wrapper that accepts __stdcall. Even though __cdecl is identical, you still must pass functions defined as __cdecl to a wrapper that accepts __cdecl.

Example that works in 32-bit:

template<typename T, typename... Args>
struct WrapperSTD { typedef T(__stdcall *Functor)(Args...); };

template<typename T, typename... Args>
struct WrapperC { typedef T(*Functor)(Args...); };

template<typename T, typename... Args>
WrapperSTD<T, Args...> wrap(T(__stdcall *func)(Args...)) {
    return WrapperSTD<T, Args...>{};
}
template<typename T, typename... Args>
WrapperC<T, Args...> wrap(T(*func)(Args...)) {
    return WrapperC<T, Args...>{};
}

My goal was to be able to run, for example:

using MsgBoxProto = decltype(wrap(MessageBoxA))::Functor;

This is fine for 32-bit. However, since __stdcall and __cdecl are apparently identical in x64, it will not work in 64-bit and raises an error saying that the calls are Ambiguous. It also tells me that the template has already been defined. Intuitively, it seems like I would be able to pass functions with __cdecl into this __stdcall function since the compiler sees them as identical. However, this does not work:

template<typename T, typename... Args>
struct WrapperFC { typedef T(__stdcall *Functor)(Args...); };

template<typename T, typename... Args>
WrapperFC<T, Args...> wrap(T(__stdcall *func)(Args...)) {
    return WrapperFC<T, Args...>{}; // same as below
}

template<typename T, typename... Args>
WrapperFC<T, Args...> wrap(T(__cdecl *func)(Args...)) {
    return WrapperFC<T, Args...>{}; // same as above
}

Error C2995 'WrapperFC<T,Args...> wrap(T (__cdecl *)(Args...))': function template has already been defined

If I only keep one of them, I cannot wrap both of these functions at the same time:

void __cdecl foo(int i){}
void __stdcall bar(int i){}

If the compiler sees them as the same, why does it require me to have different templates for accepting the different calling conventions? And when I do, why does it completely break and say it's ambiguous and already defined?

TL;DR:

If the calling conventions in 64-bit architectures are identical, why is it that I cannot pass one calling convention into a function that expects another? And how can I fix it?

like image 902
Goodies Avatar asked May 05 '16 17:05

Goodies


2 Answers

Leaving out calling convention for x64 works. This compiles for me:

template<typename T, typename... Args>
struct WrapperC { typedef T(*Functor)(Args...); };


template<typename T, typename... Args>
WrapperC<T, Args...> wrap(T(*func)(Args...)) {
    return WrapperC<T, Args...>{};
}

void __cdecl foo(int i){}
void __stdcall bar(int i){}

int main()
{
    wrap(foo);
    wrap(bar);
    using ProtoFoo = decltype(wrap(foo))::Functor;
    using ProtoBar = decltype(wrap(bar))::Functor;
}

I know it is old question and it might relate to older Visual Studio version bug, which is fixed now.

like image 125
Alex Guteniev Avatar answered Nov 19 '22 23:11

Alex Guteniev


As per the documentation, the __stdcall is ignored, so it would be like you have two __cdecl instead of one of each. That's why you can't have both.

like image 45
José Lucas Avatar answered Nov 19 '22 22:11

José Lucas