8

At the moment I'm building functors (callable types) for the different calling conventions (__stdcall, __cdecl, __fastcall etc.). With the wrappers I'll be able to do something like this:

void __stdcall foo(int arg)
{
    std::printf("arg: %i\n", arg);
}

int main(int, char**)
{
    Function<void, int> v{foo};
    v(1337);

    return EXIT_SUCCESS;
}

At the moment I have built a wrapper for the __stdcall calling convention that is able to call any __stdcall function as long as the correct parameters are specified and the correct arguments are passed in. The class looks like this:

template <typename ReturnT, typename... Args>
class Function
{
    // NOTE: This version of my callable types
    // only supports the __stdcall calling
    // convention. I need support for __cdecl,
    // __fastcall and also __thiscall.
    using return_t = ReturnT;
    using callable_t = return_t(__stdcall*)(Args...);

private:
    callable_t mCallable;

public:
    template <typename FuncT>
    Function(FuncT const &func) :
        mCallable(func)
    {
        ;
    }

    void operator()(Args&&... args)
    {
        mCallable(std::forward<Args>(args)...);
    }
};

With that in hand I decided to build the other wrappers but I figured that typing out the same piece of code and changing the calling convention inside of the using declaration for callable_t is more work than needed. So I wanted to find a way to build about 4 variants of callable types (for each calling convention) but couldn't find a way to do it.

So far I have tried to use an enum as a non-type template parameter like this:

template <CallingConvention Call, typename ReturnT, typename... ArgsT>
class Function
{
    // ...
};

But I don't know how to iterate the Call object's type and establish the required type (I tried utilizing std::is_same/std::enable_if but that was a dead end). I also tried template specialization with code like this:

struct StdcallT { ; };
struct CdeclT { ; };
struct FastcallT { ; };

template <typename CallT>
struct BaseT { };

template <> struct BaseT<StdcallT> { using CallableT = void(__stdcall*)(); };
template <> struct BaseT<CdeclT> { using CallableT = void(__cdecl*)(); };
template <> struct BaseT<FastcallT> { using CallableT = void(__fastcall*)(); };

template <typename CallT>
class Function
{
    using CallableT = typename BaseT<CallT>::CallableT;
};

But I wasn't thinking of the rest of the arguments (return type + parameters) so this can't work too.

So does anyway have any ideas what I can do? One method I'm thinking of is doing a switch on the non-type parameter and calling the correct one like this:

template <CallingConvention Call, typename ReturnT, typename... ArgsT>
class Function
{
    void operator()(ArgsT&&... args)
    {
        switch(Call)
        {
            case CallingConvention::Cdecl:
                // Call a __cdecl version
                break;
            case CallingConvention::Stdcall:
                // Call an __stdcall version
                break;
            // And so on...
        }
    }
};

And despite this looking like a working solution I was wondering if there was some good alternatives that I'm not thinking of.

Any ideas?

Michael Kiros
  • 563
  • 4
  • 11
  • Why reinvent the wheel? `std::function` should already be able to store any function pointer or functor, regardless of calling convention, so long as it's callable with the arguments you specify. Does it not work for you, or does it have some concrete drawbacks that got you starting on your custom implementation? –  Dec 29 '15 at 20:48
  • @hvd suppose you have an std::unordered_map mapping strings like "glCreateProgram" and "glCreateShader" loaded from opengl32.dll to their symbol addresses. You want to create a templated function function_cast that you will use void for no args/returns, and std::tuple for args/no args, since they are binary compatible. as it stands, std:function does not work for stdcall, so a runtime if check is needed on some dummy calling convention based on which you switch calling convention casting. – Dmytro Dec 12 '17 at 22:46
  • @Dmitry "std:function does not work for stdcall" -- Older versions of GCC (up to GCC 5) ignored the calling convention in name mangling, causing linker errors, but this was fixed mid-2015, and there shouldn't be problems with `std::function` for non-default calling conventions since then at least in that compiler. Other compilers *should* be working the same way. If one doesn't, can you give details on which one it is? –  Dec 12 '17 at 23:14
  • It doesn't work in gcc.exe (GCC) 5.3.0, and visual studio 2012. You're saying newer versions of C++ have allowed you to pass a c++ lambda to [CreateThread](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx) without writing your own stdcall wrapper to pass cdecl function as an argument? – Dmytro Dec 12 '17 at 23:32

2 Answers2

1

If you still want to use the enumerated template argument, you can use specialization to accomplish this.

enum CallingConvention { __stdcall, ... };

template < CallingConvention Call >
struct implement {
    template</* Template arguments for call method */>
    static ReturnT call(/* arguments to run method */);
};

template < CallingConvention Call, typename ReturnT, typename... ArgsT >
class Function
{
    // ...
    template <typename FuncT>
    Function(FuncT const &func) : mCallable(func), mCall(Call) {}
    CallingConvention const mCall;

    return_t operator()(ArgsT&&... args) {
        return implement<Call>::call</* Template arguments for call method */>(/* arguments to run method */);
    };
};

template < >
struct implement< __stdcall > {
    template</* Template arguments for call method */>
    static ReturnT call(/* arguments to run method */) {
        // Special implementation...
    }
};

That will be better than a switch statement.

(Sorry about the comments for the template arguments I am not quite familiar with how that works)

Here is where I got the idea for what I did.


Hope this helps!

Community
  • 1
  • 1
tkellehe
  • 659
  • 5
  • 11
1

Well once you define tags for each calling convention, you can use tag dispatch regularly:

#include <iostream>
#include <type_traits>

struct cdecl_tag    { typedef void ( __attribute__((cdecl))    *type)(); };
struct stdcall_tag  { typedef void ( __attribute__((stdcall))  *type)(); };
struct fastcall_tag { typedef void ( __attribute__((fastcall)) *type)(); };

constexpr void get_func_calling_convention_tag () {};

template<typename R, typename... Args>
constexpr cdecl_tag
get_func_calling_convention_tag (R (__attribute__((cdecl)) *)(Args...))
{ return {}; }

template<typename R, typename... Args>
constexpr stdcall_tag
get_func_calling_convention_tag (R (__attribute__((stdcall)) *)(Args...))
{ return {}; }

template<typename R, typename... Args>
constexpr fastcall_tag
get_func_calling_convention_tag (R (__attribute__((fastcall)) *)(Args...))
{ return {}; }

#define CALLING_CONVENTION_TAG(func) \
decltype(get_func_calling_convention_tag(&func))

int  __attribute__((cdecl))   foo (char) { return 0; }
long __attribute__((stdcall)) bar (int)  { return 0; }

int main()
{
    std::cout << std::is_same<CALLING_CONVENTION_TAG(foo),
                              cdecl_tag>::value                   << '\n'
              << std::is_same<CALLING_CONVENTION_TAG(bar),
                              stdcall_tag>::value                 << '\n'
              << std::is_same<CALLING_CONVENTION_TAG(foo), 
                              CALLING_CONVENTION_TAG(bar)>::value << std::endl;

    return 0;
}

See it in action : http://ideone.com/HSZztX
This can of course be developed further; the tags may have a rebind variadic member template that returns a function pointer type with the appropriate calling convention specified.

I suppose you may even reduce the copying and pasting by having tag defintions neatly in a macro.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458