在不事先知道参数数量的情况下调用函数

calling a function without knowing the number of parameters in advance

本文关键字:情况下 调用 函数 数数 参数 先知      更新时间:2023-10-16

假设我有一个带有两个函数的dll。dll的名称="dll1"f1(int a,int b,int c);

f2(int a);

我的程序会将函数名、dll名和参数"列表"作为输入。我该如何调用具有适当参数的适当函数。即,如果输入dll1f1列表(5,8,9)

这需要我用3个参数调用f1如果输入dll1f2列表(8)它需要我用一个参数调用f2如果事先不知道参数的数量,我该如何调用函数呢。

进一步澄清:如何编写将调用any的代码通过动态构建参数列表来使用其所有参数使用其他信息源

由于生成的代码根据参数的数量而不同,您有两种选择:您可以用汇编语言编写一些代码来完成这项工作(基本上是在调用函数之前遍历参数列表并将每个参数推送到堆栈上),或者您可以创建类似于函数指针数组的东西,您关心的参数(例如,0到10)的数量各有一个。大多数人发现后者更容易处理(如果只是因为它根本避免使用汇编语言的话)。

要解决一般问题,您需要知道:

  1. 调用约定(stdcallcdeclfastcallhiscall(顺便说一句,后两者可以在MSVC++中组合)等)控制函数如何接收参数(例如,在特殊寄存器中,在堆栈上,两者都有)、如何返回值(相同)以及允许丢弃什么(例如,一些寄存器)
  2. 精确的函数原型

您只能在编译器生成的符号/调试信息以及(可能在较小程度上)包含DLL中函数原型的头文件中找到所有这些信息。头文件有一个问题。如果它没有指定调用约定,并且函数是用非默认调用约定编译的(通过编译器选项),那么就有歧义需要处理。无论哪种情况,您都需要解析某些内容。

如果您没有这些信息,剩下的唯一选项是DLL和/或其用户的反向工程。

为了在运行时只知道其原型和调用约定的情况下正确调用任意函数,您需要构造与编译器在编译时已知调用该函数时生成的代码类似的代码。如果您要解决一般问题,您需要一些汇编代码,而不一定是手工编写的,运行时生成的机器代码是一个不错的选择。

最后但同样重要的是,您需要一些代码来生成参数值。这对于数字类型(int、float等)及其数组来说是最琐碎的,而对于结构、并集和类来说则是最困难的。动态创建后者可能至少与正确调用函数一样困难。不要忘记,它们可能使用指针和引用来引用其他对象。

一般问题是可以解决的,但代价不菲。解决一些简单的特定情况要容易得多,通过重写函数使其具有更少的可变参数和只有一个调用约定,或者通过编写包装函数来实现这一点,可能会完全避免整个问题。

您可能需要查看命名参数习语。

它使用方法链接来基本上完成您想要的。

它解决了这样一个问题,即您知道默认的参数集是什么样子的,但只需要自定义其中的一些,而不一定按照它们的声明顺序。

如果您的客户在编译时知道,那么可以这样包装:

template<class Args...>
void CallFunctionPointer(void* pf, Args&&... args)
{
    typedef void(*FunctionType)(Args...);
    FunctionType* pf2 = (FunctionType*) pf;
    (*pf2)(forward<Args>(args)...);
}

请注意,如果您传递了错误数量的参数或错误类型的参数,则行为是未定义的。

背景:

在C/C++中,您可以将函数指针强制转换为所需的任何签名,但如果您得到了错误的签名,则行为是未定义的。

在你的案例中,有两个你提到的签名:

void (*)(int)

void (*)(int, int, int)

当您从DLL加载函数时,您有责任确保在调用它之前将其转换为正确的签名,并使用正确的参数数量和类型。

如果您可以控制这些函数的设计,我会修改它们,使其具有可变数量的参数。如果基类型始终为int,则不只是将所有函数的签名更改为:

void (*)(int* begin, size_t n);
// begin points to an array of int of n elements

以便您可以安全地将任何函数绑定到任意数量的参数。