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

2

6

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?

Drake answered 5/5, 2016 at 17:12 Comment(8)
Also, it does not work if I leave out a calling convention altogether. Finally, I've tried using std::function to no avail.Drake
Calling convention is part of the function type. C++ neither knows nor cares that your particular OS and platform have defined each convention to be implemented the same. Is that the answer? (That being said, isn't this all an extension anyway?)Hothouse
If the calling conventions really were the same, it would be akin to passing a long into a function that accepts an int. They are more or less the same (on my PC anyways).Drake
Two types that happen to have the same properties is not the same as one type. For example, contrast char vs unsigned char (or signed char; depends on your platform) with int vs signed int. Similarly, int and long are distinct types, even if both are 32-bit two's complement. You need to mentally separate definition from implementation. But I have no idea whether any of this is relevant to your question :o)Hothouse
It looks like I can just nix this entire thing by just using decltype, but I think my point stands. The compiler should be confused about one thing and not about the other. It' sure that they're the same, or it would let me define multiple functions with different calling conventions. It's also sure that they're different, because it won't let me pass one to another.Drake
You're still confusing specification for implementation. You're still confusing identity for equivalence.Hothouse
I do understand that to an extent, however, why would they act identically in one context and then act differently in another? They are the same when I define them since it gives me what is essentially a "This function is already defined" error, but they act differently when I pass them as parameters. THAT is what I'm confused on.Drake
Yeah I dunno about thatHothouse
C
2

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.

Cinerama answered 23/2, 2019 at 18:12 Comment(0)
K
1

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.

Knighterrant answered 7/5, 2021 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.