【问题标题】:Calling a C function with a varargs argument dynamically动态调用带有可变参数参数的 C 函数
【发布时间】:2021-11-30 13:56:57
【问题描述】:

我在 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 的常量来标记参数列表的结尾。原始实现不使用它。大概是调用站点..

【问题讨论】:

  • 代码损坏:序列点之间 i 和 j 的多次修改。
  • 嘿,哇,这是老东西了。
  • 对于许多试图回答这个问题的发帖人,请注意 Loadrunner(当时)不支持人们可能习惯的标准 C 的许多细节。值得注意的是:除了 HP 提供的库之外,没有其他库。几乎不支持宏 - 不确定它是否有预处理器。类型定义?可能不是。等等等等。

标签: c


【解决方案1】:

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

【讨论】:

  • 这个答案是金。我也在使用 objc_msgSend()。
  • 这可能是唯一正确的答案。没有简单的独立于平台的解决方案。因此,请使用将平台抽象出来的库。另一个这样的库是dyncall
【解决方案2】:

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

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

设计可变参数函数的正确方法是实际上有两个版本,一个接受“...”,然后提取 va_list 并将其传递给接受 va_list 的函数。这样,您可以在需要时动态构造参数,并且可以改为调用函数的 va_list 版本。

大多数标准 IO 函数都有可变参数版本:vprintf 用于 printf,vsprintf 用于 sprintf……你明白了。看看你的库是否实现了一个名为“vweb_submit_data”的函数或类似的东西。如果他们不这样做,请给他们发电子邮件并告诉他们修复他们的库。

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

【讨论】:

    【解决方案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)。因此,使用此技术需要您自担风险,并验证、验证、验证)。

    【讨论】:

    • 兼容与否,这是我长期使用(并且看到使用)的一种技术。
    • @michael-burr 是否符合,感谢您的回答,谢谢。
    【解决方案4】:

    没有可移植的方法在运行时为 C 中的可变参数函数构建参数列表。那里有一些依赖于实现的tricks,您发现的 dyncall 库看起来不错,并且可能比大多数更便携。

    【讨论】:

    • 我喜欢至少在其中一个“技巧”中使用的命名约定:va_stack、va_push 和 va_call。这听起来像是我应该添加到我的小袋子技巧库中的东西。谢谢。
    【解决方案5】:

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

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

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

    【讨论】:

    • 它比实现依赖更糟糕 - 它是未定义的,因为 i 和 j 被多次修改而没有中间序列点。
    【解决方案6】:

    您能否重组您的代码以使其不再需要?也许您可以获取传入缓冲区并使其更具确定性:

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

    很抱歉,这不能再充实了 - 我的妻子叫我去吃早餐。 :-)

    【讨论】:

      【解决方案7】:

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

      #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++ 在序列点之间出现多次。就像问题中一样。
      【解决方案8】:

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

      更不用说函数调用参数的求值顺序并没有被标准指定——所以即使你修改了ij使用函数来求值(函数调用本身就是顺序点),您几乎会以不确定的顺序传递指针。

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

      【讨论】:

        【解决方案9】:

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

        您不能动态定义“...”接口的参数数量,但您应该能够为 va_list 之一这样做。 Google 搜索 va_start、va_end 和 va_list。

        【讨论】:

          【解决方案10】:

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

          将一次调用记录为 web_custom_request(),名称|值对的参数字符串的结构将变得显而易见。只需使用您希望构建相关字符串的任何 C 字符串处理函数,并将其作为 web_custom_request() 的参数列表的一部分。

          【讨论】:

            猜你喜欢
            • 2021-08-31
            • 2023-03-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-03-31
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多