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

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

我的一个前任所做的function有点服务但这里的问题是这个函数假定最坏的情况(超过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的常量来标记参数列表的结尾。 原始实现不使用它。 据推测,呼叫网站确实……

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

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

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

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

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

3000条同样的东西(即使它是预处理器诱导的)让我感到畏缩

因为将一个参数传递给一个函数来获取比函数所期望的变量参数通常不是问题(参见脚注#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 )。 因此,使用此技术需要您自担风险,并validation,validation,validation)。

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

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

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

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

你可以重组你的代码,这是不必要的吗? 也许您可以使用传入缓冲区并使其更具确定性:

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

对不起,这不可能更加充实 – 我的妻子打电话给我吃早餐。 🙂

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

 #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 */ 

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

这并不是说标准没有指定函数调用参数的评估顺序 – 所以即使你使用函数来修改ij来计算参数(函数调用本身就是序列点),你也会很漂亮以不确定的顺序传递指针。

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

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

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

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

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