Динамический вызов функции C с аргументом varargs - PullRequest
15 голосов
/ 11 ноября 2008

Я программирую на 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), но я бы предпочел не а) полностью зависеть от процессора и б) попытаться научить Loadrunner ссылкам во внешних источниках.

Edit: Оригинальная функция использовала жестко закодированные индексы вместо использования переменной. Можно все же вернуться к этому, если окажется слишком непредсказуемым. Однако, поскольку я вряд ли смогу запустить это с другим компилятором или оборудованием / ОС, я сомневаюсь, что это действительно того стоит.

Также: у меня нет контроля над реализацией web_submit_data (). Таким образом, просто отодвинуть проблему вниз на один уровень не удастся ее урезать.

Еще один момент, на который следует обратить внимание: спецификация для web_submit_data() использует константу с именем LAST, чтобы отметить конец списка аргументов. Оригинальная реализация не использует его. Предположительно, это место звонка ..

Ответы [ 10 ]

9 голосов
/ 12 ноября 2008

В CamelBones я использую libffi для вызова objc_msgSend (), который является функцией varargs. Работает угощение.

8 голосов
/ 12 ноября 2008

Аргументы переменной длины - это просто указатель на группу упакованных данных, которые передаются в требуемую функцию. Вызываемая функция отвечает за интерпретацию этих упакованных данных.

Безопасный для архитектуры способ сделать это - использовать макросы va_list (упомянутые n-alexander), в противном случае вы можете столкнуться с проблемами, связанными с заполнением различных типов данных в памяти.

Правильный способ разработки функций varargs - фактически иметь две версии, одна из которых принимает «...», которая, в свою очередь, извлекает va_list и передает его функции, которая принимает va_list. Таким образом, вы можете динамически создавать аргументы, если вам нужно, и вместо этого можете вызвать версию функции va_list.

Большинство стандартных функций ввода-вывода имеют версии varargs: vprintf для printf, vsprintf для sprintf ... Вы понимаете. Посмотрите, реализует ли ваша библиотека функцию с именем "vweb_submit_data" или что-то в этом роде. Если они этого не сделают, напишите им по электронной почте и попросите их починить библиотеку.

3000 строк одного и того же (даже если это вызвано препроцессором) заставляет меня съеживаться

3 голосов
/ 11 ноября 2008

Поскольку, как правило, не проблема передать больше аргументов в функцию, принимающую переменные аргументы, чем эта функция ожидает (см. Сноску # 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, это становится понятным. Однако я не утверждаю, что этот метод будет соответствовать стандартам. Фактически, в недавней истории ответы на стекопоток, которые я разместил, где я сделал этот отказ от ответственности, на самом деле были признаны не соответствующими стандартам (как правило, бдительными litb ). Так что используйте эту технику на свой страх и риск, и проверяйте, проверяйте, проверяйте).

2 голосов
/ 11 ноября 2008

Не существует переносимого способа создания списка аргументов для функции переменного аргумента в C во время выполнения. Существует несколько зависимых от реализации трюков , библиотека dyncall, которую вы нашли, выглядит хорошей и, вероятно, более переносимой, чем большинство.

1 голос
/ 12 ноября 2008

Существует два способа передачи переменного числа аргументов: функции, которая принимает «...», или функции, которая принимает va_list.

Вы не можете динамически определять количество аргументов для интерфейса "...", но вы должны быть в состоянии сделать это для va_list. Google для va_start, va_end и va_list.

1 голос
/ 11 ноября 2008

Обратите внимание, что пример кода, который вы опубликовали, имеет неопределенное поведение - запятые, которые разделяют параметры функции, не являются точками последовательности (эти запятые не являются оператором запятой), поэтому несколько раз изменяйте i или j в вызове функции список аргументов приводит к неопределенному поведению.

Это не говоря уже о том, что порядок оценки аргументов вызова функции не определен стандартом - поэтому даже если вы выполняли модификацию i и j, используя функции для оценки аргументов (сами вызовы функций являются последовательностью баллы), вы бы в значительной степени передавали указатели в неопределенном порядке.

Кроме того, я не вижу, как web_submit_data() знает, сколько аргументов было передано - я не вижу счетчика или окончательного аргумента дозорного в конце. Но я думаю, что ваш пример может быть именно таким - пример, который может не иметь полных, точных деталей. С другой стороны, это проблема web_submit_data() в любом случае, верно?

1 голос
/ 11 ноября 2008

Напишите это один раз с препроцессором и никогда не оглядывайтесь назад.

#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 */
1 голос
/ 11 ноября 2008

Можете ли вы реструктурировать свой код так, чтобы в этом не было необходимости? Возможно, вы могли бы взять входящий буфер и сделать его более детерминированным:

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

}

Извините, это не могло быть более конкретным - моя жена позвала меня на завтрак : -)

1 голос
/ 11 ноября 2008

Примечание: код уже зависит от компилятора (хотя, возможно, не зависит от процессора), потому что при вызове web_submit_data предполагается, что подвыражения аргумента в вызове процедуры вычисляются слева направо, но Язык C оставляет порядок вычисления аргументов неуказанным.

См. Для справки: http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_value

Так что, возможно, непереносимое решение не ухудшит для вас ситуацию.

0 голосов
/ 25 января 2013

Я знаю, что это старая ветка, но я просто наткнулся на нее. Правильный способ обработки данных формы отправки переменной длины в LoadRunner - это использование web_custom_request (). Вы строите структуру пары имя | значение для переменной длины аргументов в виде строки и передаете ее как часть функции.

Запишите один вызов как web_custom_request (), и структура строки аргумента для пар имя | значение станет очевидной. Просто используйте любые функции обработки строки C, которые вы хотите создать, и включите ее в список аргументов для web_custom_request ().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...