【问题标题】:Callable Objects with different calling conventions具有不同调用约定的可调用对象
【发布时间】:2016-04-02 20:21:03
【问题描述】:

目前,我正在为不同的调用约定(__stdcall、__cdecl、__fastcall 等)构建仿函数(可调用类型)。使用包装器,我将能够执行以下操作:

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

目前我已经为 __stdcall 调用约定构建了一个包装器,只要指定了正确的参数并传入了正确的参数,它就能够调用任何 __stdcall 函数。该类如下所示:

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)...);
    }
};

有了这个,我决定构建其他包装器,但我发现键入相同的代码并更改 callable_t 的 using 声明中的调用约定比需要的工作更多。所以我想找到一种方法来构建大约 4 种可调用类型的变体(针对每种调用约定),但找不到方法。

到目前为止,我已经尝试使用枚举作为非类型模板参数,如下所示:

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

但我不知道如何迭代 Call 对象的类型并建立所需的类型(我尝试使用 std::is_same/std::enable_if 但这是一条死胡同)。我还尝试使用如下代码进行模板专业化:

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

但我没有考虑其余的参数(返回类型 + 参数),所以这也行不通。

那么无论如何我有什么想法吗?我正在考虑的一种方法是对非类型参数进行切换并像这样调用正确的参数:

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

尽管这看起来像是一个可行的解决方案,但我想知道是否有一些我没有想到的好的替代方案。

有什么想法吗?

【问题讨论】:

  • 为什么要重新发明轮子? std::function 应该已经能够存储任何函数指针或函子,无论调用约定如何,只要它可以使用您指定的参数进行调用。它对您不起作用,还是有一些具体的缺点让您开始自定义实现?
  • @hvd 假设您有一个 std::unordered_map 映射字符串,例如从 opengl32.dll 加载的“glCreateProgram”和“glCreateShader”到它们的符号地址。您想创建一个模板化函数 function_cast ,您将使用 void 来表示无参数/返回,以及 std::tuple 用于 args/无参数,因为它们是二进制兼容的。就目前而言,std:function 不适用于 stdcall,因此运行时是否需要检查一些基于您切换调用约定转换的虚拟调用约定。
  • @Dmitry "std:function doesn't work for stdcall" -- 旧版本的 GCC(直到 GCC 5)忽略了名称修改中的调用约定,导致链接器错误,但这已在中期修复2015 年,至少在那个编译器中,std::function 对于非默认调用约定应该没有问题。其他编译器应该以同样的方式工作。如果没有,你能详细说明它是哪一个吗?
  • 它在 gcc.exe (GCC) 5.3.0 和 Visual Studio 2012 中不起作用。您是说较新版本的 C++ 允许您将 c++ lambda 传递给 CreateThread 而无需编写自己的 stdcall 包装器以将 cdecl 函数作为参数传递?

标签: c++ callable


【解决方案1】:

如果您仍想使用枚举模板参数,您可以使用特化来完成此操作。

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

这将比 switch 语句更好。

(对不起,模板参数的 cmets 我不太熟悉它是如何工作的)

这是我对 did 的想法的地方。


希望这会有所帮助!

【讨论】:

    【解决方案2】:

    一旦你为每个调用约定定义了标签,你就可以定期使用标签调度:

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

    查看实际操作:http://ideone.com/HSZztX
    这当然可以进一步发展;标签可能有一个重新绑定可变参数成员模板,该模板返回一个函数指针类型,并指定了适当的调用约定。

    我想你甚至可以通过在宏中整齐地定义标签来减少复制和粘贴。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-21
      • 1970-01-01
      • 1970-01-01
      • 2019-04-29
      • 2014-10-02
      • 2018-09-30
      • 2021-02-18
      • 1970-01-01
      相关资源
      最近更新 更多