Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Callable Objects with different calling conventions

Tags:

c++

callable

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?

like image 957
Michael Kiros Avatar asked Dec 29 '15 00:12

Michael Kiros


People also ask

What do you mean by calling conventions?

A calling convention governs how functions on a particular architecture and operating system interact. This includes rules about includes how function arguments are placed, where return values go, what registers functions may use, how they may allocate local variables, and so forth.

What are calling conventions in C++?

Calling conventions specify how arguments are passed to a function, how return values are passed back out of a function, how the function is called, and how the function manages the stack and its stack frame. In short, the calling convention specifies how a function call in C or C++ is converted into assembly language.

What is difference between cdecl and Stdcall?

__cdecl is the default calling convention for C and C++ programs. Because the stack is cleaned up by the caller, it can do vararg functions. The __cdecl calling convention creates larger executables than __stdcall, because it requires each function call to include stack cleanup code.

What is Winapi calling convention?

A calling convention is a scheme for how functions receive parameters from their caller and how they return a result. The calling conventions can differ in where parameters and return values are placed (in registers; on the call stack; a mix of both), the order they are placed.


2 Answers

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!

like image 122
tkellehe Avatar answered Nov 04 '22 03:11

tkellehe


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.

like image 20
StoryTeller - Unslander Monica Avatar answered Nov 04 '22 03:11

StoryTeller - Unslander Monica