为 __stdcall 函数指针提供比预期更多的参数

Posted

技术标签:

【中文标题】为 __stdcall 函数指针提供比预期更多的参数【英文标题】:Giving a __stdcall function pointer more arguments than it expects 【发布时间】:2014-01-22 19:16:43 【问题描述】:

我正在从供应商提供的 dll 中导入大约 1500 个函数(全部为 __stdcall)。由于繁琐的原因,dll 存在于许多版本中,其中包含完整函数列表的各种子集(尽管确实存在的所有函数都有公共接口)。所有函数都返回一个指示错误代码的 UINT,但采用不同数量的不同类型的参数。目前如果GetProcAddress失败(因为该函数在特定的dll版本中不存在),函数指针为left = nullptr,每次客户端从dll调用函数时都需要检查该函数指针。相反,我想将指针分配给返回适当错误代码的 UINT 的函数。建议的解决方案是这样的:

UINT missing_func() 
   return ERR_MISSING_FUNC;

.....
typedef UINT(*LibFuncTy)(int a, const char *b, double c);
.....
//GetProcAddress returned nullptr... set generic function
LibFuncTy LibFunc = reinterpret_cast<LibFunctTy>(missing_func);
.....
//programme tries to call library function
errorval = LibFunc(arg1, arg2, arg3);

这种方法按书面方式工作(尽管标准明确未定义),但一旦调用是 __stdcall 就会失败(可能是因为我们弄乱了堆栈)。我看过使用(/滥用)绑定,但似乎对我正在尝试做的事情没有帮助(除非我错过了一些东西)。许多库函数调用最终都处于深度嵌套循环中,因此我不想在 GetProcAddress 能够找到该函数的情况下产生任何(非微不足道的)开销。

是否有一种“正确”的、符合标准的、实现这种行为的方法,它可以与 __stdcall 调用约定一起使用,还是我必须生成 1500 个不同版本的 missing_func() 以及不同的参数列表,所有这些都返回相同的价值?

【问题讨论】:

编译或执行过程中是否失败? 对,你搞砸了堆栈。改为抛出异常。 它失败是因为 stdcall 依赖被调用者从堆栈中弹出参数,而missing_func() 不会弹出任何内容。希望您导入的具有不同签名的函数的数量远小于 1500。最简单但乏味的解决方案是为每个函数创建 missing_func() 重载,并适当地分配它们。 签名确实少于 1500 个,但超过 100 个签名仍然很繁琐,手动生成和分配。目前刚刚自动处理函数列表以创建所有“missing_func”克隆,以牺牲一些冗余为代价...... 【参考方案1】:

每个堆栈帧大小都需要一个虚拟函数,因为此约定的 ret 指令会相应地调整堆栈指针。

请注意,“未实现”有一个标准错误代码,我认为像E_NOTIMPL 这样的符号名称。看看吧。

【讨论】:

【参考方案2】:

在调度对导入函数的调用之前创建一个检查nullptr 的包装器似乎可行。

template<typename... Args>
struct get_proc_address_wrapper;

template<typename... Args>
struct get_proc_address_wrapper<unsigned(Args...)>

    typedef unsigned(__stdcall *func_type)(Args...);
    get_proc_address_wrapper(func_type pf)
        : pf(pf)
    

    unsigned operator()(Args... args)
    
        if (not pf) return static_cast<unsigned>(-1);
        else return (*pf)(args...);
    

    func_type pf;
;

这里有一个demo 你会如何使用它。如您所见,如果包装器使用nullptr 初始化,它会优雅地返回错误代码。我在 VS2013 上测试了相同的代码,它返回了相同的结果。

【讨论】:

完全符合我的要求 - 非常感谢。我以前没有遇到过可变参数函数,并且在 Stroustrup 中找不到任何关于它们的信息 - 我是否正确,您声明可变参数类 get_proc_address_wrapper 然后声明类型 unsigned(Args...) 的特化? @Xphraz 我正在使用variadic templates。 Stroustrup 书中的4th edition 谈到了它们。并且不需要专门化,它只是允许您在创建实例时使用get_proc_address_wrapper&lt;Ret(Arg1, Arg2)&gt; 语法,我觉得声明函数签名更自然。

以上是关于为 __stdcall 函数指针提供比预期更多的参数的主要内容,如果未能解决你的问题,请参考以下文章

_stdcall 函数 debug/release汇编代码区别

函数调用方法之__cdecl与_stdcall

_stdcall 与 _cdecl 区别

_stdcall 与 _cdecl 区别

__stdcall __cdecl的区别

_STDCALL&_CDECL 调用约定