动态调用带有varargs参数的C函数

时间:2008-11-11 13:57:07

标签: c

我正在使用C编程来对抗第三方库(在HP / Mercury Loadrunner中),该库为其中一个函数提供了varargs样式的可变大小参数列表。我想调用这个函数,但我不知道我将拥有多少个参数。

我的一位前任所做的功能在某种程度上有所帮助,但这里的问题是这个函数假设最坏的情况(超过3000个参数)和手动代码。

要点亮,这是代码的(开头)。我们称之为web_submit_data()的函数。 HTTP将发布一组表单数据。在处理具有任意数量字段的动态生成的表单时,会出现此实现。 (从原版中清理了一下,手动编码索引也是如此......)


web_submit_data_buffer_gazillion_items( const char *bufferName, const char *bufferValue)
{
    const int size = 129;
    int i = 0;
    int j = 11;

    web_submit_data(&bufferName[i++ * size], //"some form"
                &bufferName[i++ * size], //"Action=https://blah.blah/form");
                &bufferName[i++ * size], //"Method=POST");
                &bufferName[i++ * size], //"TargetFrame=");
                &bufferName[i++ * size], //"RecContentType=text/html");
                &bufferName[i++ * size], //"Referer=https://blah.blah/index.html");
                &bufferName[i++ * size], //"Snapshot=t1.inf");
                &bufferName[i++ * size], //"Mode=HTML");
                ITEMDATA,  // missing in action: indexes 8 through 10
                &bufferName[j * size],&bufferValue[j++ * size], ENDITEM, 
                &bufferName[j * size],&bufferValue[j++ * size], ENDITEM, 
                &bufferName[j * size],&bufferValue[j++ * size], ENDITEM, 
..
(repeat the last 3 lines ad nauseum)
..
                &bufferName[j * size],&bufferValue[j++ * size], ENDITEM,
                &bufferName[j * size]);  
}

现在我找到了一个可以工作的外部库(http://www.dyncall.org),但我宁愿不a)完全依赖于处理器,b)尝试教会Loadrunner关于外部源的链接..

编辑: 原始函数使用硬编码索引而不是使用变量。如果结果太难以预测,仍然可以恢复原状。但是,由于我不太可能使用不同的编译器或硬件/操作系统来运行它,我怀疑这是否值得。

另外:我无法控制web_submit_data()的实现。因此,将问题推向一个层次并不会削减它..

需要注意的另一件事:web_submit_data()的规范使用一个名为LAST的常量来标记参数列表的结尾。原始实现不使用它。据推测,呼叫网站确实......

10 个答案:

答案 0 :(得分:9)

在CamelBones中,我使用libffi来调用objc_msgSend(),这是一个varargs函数。是一种享受。

答案 1 :(得分:8)

可变长度参数基本上只是指向传递给所​​需函数的一堆打包数据的指针。被调用函数负责解释此打包数据。

架构安全的方法是使用va_list宏(提到n-alexander),否则你可能会遇到各种数据类型在内存中填充的问题。

设计varargs函数的正确方法是实际上有两个版本,一个接受'...',然后提取va_list并将其传递给一个带有va_list的函数。这样,您可以根据需要动态构造参数,而可以调用该函数的va_list版本。

大多数标准IO函数都有varargs版本:vprintf for printf,vsprintf for sprintf ...你明白了。查看您的库是否实现了名为“vweb_submit_data”的函数或类似的效果。如果他们不这样做,请给他们发电子邮件并告诉他们修理他们的图书馆。

3000行相同的东西(即使它是预处理器引起的)让我感到畏缩

答案 2 :(得分:3)

由于将更多参数传递给采用变量参数而不是函数期望的函数通常不是问题(参见脚注#1),您可以执行以下操作:

// you didn't give a clear specification of what you want/need, so this 
// example may not be quite what you want as I've had to guess at
// some of the specifications. Hopefully the comments will make clear
// what I may have assumed.
//
// NOTE:  while I have compiled this example, I have not tested it,
//        so there is a distinct possiblity of bugs (particularly
//        off-by-one errors). Check me on this stuff, please.

// I made these up so I could compile the example
#define ITEMDATA ((char const*) NULL)
#define ENDITEM  ((char const*) 0xffffffff)

void web_submit_data_wrapper( const char*bufferName, 
                              const char* bufferValue, 
                              size_t headerCount,       // number of header pointers to pass (8 in your example)
                              size_t itemStartIndex,    // index where items start in the buffers (11 in your example)
                              size_t itemCount,         // number of items to pass (unspecified in your example)
                              size_t dataSize )         // size of each header or item (129 in your example)
{
    // kMaxVarArgs would be 3000 or a gazillion in your case

    // size_t const kMaxVarArgs = 20;  // I'd prefer to use this in C++
    #define kMaxVarArgs (20)

    typedef char const* char_ptr_t;
    typedef char_ptr_t char_ptr_array_t[kMaxVarArgs];

    char_ptr_array_t varargs = {0};

    size_t idx = 0;

    // build up the array of pararmeters we'll pass to the variable arg list

    // first the headers
    while (headerCount--) {
        varargs[idx++] = &bufferName[idx * dataSize];
    }

    // mark the end of the header data
    varargs[idx++] = ITEMDATA;

    // now the "items"
    while (itemCount--) {
        varargs[idx++] = &bufferName[itemStartIndex * dataSize];
        varargs[idx++] = &bufferValue[itemStartIndex * dataSize];
        varargs[idx++] = ENDITEM;

        ++itemStartIndex;
    }

    // the thing after the last item 
    // (I'm not sure what this is from your example)
    varargs[idx] = &bufferName[itemStartIndex * dataSize];

    // now call the target function - the fact that we're passing more arguments
    //  than necessary should not matter due to the way VA_ARGS are handled 
    //  but see the Footnote in the SO answer for a disclaimer

    web_submit_data( 
        varargs[0],
        varargs[1],
        varargs[2],

        //... ad nasuem until

        varargs[kMaxVarArgs-1]
        );

}

脚注#1:如果您考虑stdargs.h中的宏如何变得清晰。但是,我并不认为这种技术符合标准。事实上,在最近的历史中,stackoverflow回答我发布的地方我已经发现这个免责声明事实上被发现是非标准的(通常由警惕的litb)。因此,使用此技术需要您自担风险,并验证,验证,验证)。

答案 3 :(得分:2)

在运行时,没有可移植的方法来为C中的变量参数函数建立参数列表。有一些依赖于实现的tricks,你发现的dyncall库看起来很好,可能比大多数都便携。

答案 4 :(得分:1)

注意:代码已经依赖于编译器(尽管可能不依赖于处理器),因为web_submit_data的调用假定过程调用中的参数子表达式是按从左到右的顺序进行计算的,但是C语言没有指定论证评估的顺序。

参见参考:http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_value

因此,非便携式解决方案可能不会让您的情况变得更糟。

答案 5 :(得分:1)

您可以重新构建代码,以便不需要这样做吗?也许您可以使用传入缓冲区并使其更具确定性:

struct form_field
{
  char[FIELD_NAME_MAX] name;
  char[FIELD_VALUE_MAX] val;
};

web_submit_data_buffer_gazillion_items( const char *bufferName, const char *bufferValue)
{
    /*
      loop over bufferName somehow, either with a known size or terminating record,
      and build an array of form_field records
    */
    //loop
    {
      // build array of records
    }


    web_submit_data(record_array, array_len);

}

抱歉这不可能更加充实 - 我的妻子打电话给我吃早餐。 : - )

答案 6 :(得分:1)

使用预处理器写一次,永不回头。

#define WEB_SUBMIT_BUFFER(name, val)         \
    do {                                     \
        const int size = 129;                \
        int i = 0;                           \
        int j = 11;                          \
        web_submit_data(&(name)[i++ * size], \
                        &(name)[i++ * size], \
        /* etc ad nauseum */                 \
    } while (0)

或者,如果每个特定调用的参数个数是固定的,请编写一个脚本来生成预处理器定义,以隐藏该调用的可疑程度。

#define WEB_SUBMIT_BUFFER_32(name, val)      \
    do {                                     \
        const int size = 129;                \
        int i = 0;                           \
        int j = 11;                          \
        web_submit_data(&(name)[i++ * size], \
                        &(name)[i++ * size], \
        /* 32 times */                       \
    } while (0)
#define WEB_SUBMIT_BUFFER_33(name, val) ...
#define WEB_SUBMIT_BUFFER_34(name, val) /* etc */

答案 7 :(得分:1)

请注意,您发布的代码示例具有未定义的行为 - 分隔函数参数的逗号不是序列点(这些逗号不是逗号运算符),因此多次修改i和/或j在函数调用参数列表中导致未定义的行为。

这并不是说标准没有指定函数调用参数的评估顺序 - 所以即使你使用函数来修改ij来评估参数(函数)调用它们本身就是序列点),你将以不确定的顺序传递指针。

另外,我没有看到web_submit_data()如何知道它传递了多少个参数 - 我最后没有看到计数或确定的哨兵参数。但我想你的例子可能只是 - 这个例子可能没有完整,准确的细节。另一方面,无论如何都是web_submit_data()的问题,对吧?

答案 8 :(得分:1)

有两种方法可以传递可变数量的参数:接受“...”的函数或接受va_list的函数。

您无法动态定义“...”接口的参数数量,但您应该能够为va_list接口执行此操作。 Google for va_start,va_end和va_list。

答案 9 :(得分:0)

我知道这是一个旧线程,但我只是碰到了它。在LoadRunner中处理可变长度提交表单数据的正确方法是使用web_custom_request()。您将参数的可变长度的名称|值对结构构建为字符串,并将其作为函数的一部分传递。

将一个调用记录为web_custom_request(),并且名称|值对的参数字符串的结构将变得明显。只需使用您希望构造有问题字符串的任何C字符串处理函数,并将其作为web_custom_request()的参数列表的一部分包含在内。