Ответ 1
В CamelBones я использую libffi для вызова objc_msgSend(), который является функцией varargs. Работает с удовольствием.
Я программирую на 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 обложению во внешних источниках.
Изменить: Исходная функция использовала жестко кодированные индексы вместо использования переменной. Может все же вернуться к этому, если окажется слишком непредсказуемым. Однако, поскольку я вряд ли буду запускать это с помощью другого компилятора или оборудования/ОС, я сомневаюсь, что это действительно того стоит.
Также: у меня нет контроля над реализацией web_submit_data(). Так что просто нажатие проблемы на один уровень не собирается сокращать ее.
Еще одно замечание: спецификация для web_submit_data()
использует константу LAST для обозначения конца списка аргументов. Исходная реализация не использует его. Предположительно, callsite делает..
В CamelBones я использую libffi для вызова objc_msgSend(), который является функцией varargs. Работает с удовольствием.
Аргументы переменной длины - это всего лишь указатель на кучу упакованных данных, которые передаются требуемой функции. Ответственность за эту упакованную информацию несет вызываемая функция.
Безопасным для архитектуры способом сделать это является использование макросов va_list (об этом упоминался n-alexander), иначе вы можете столкнуться с проблемами с тем, как различные типы данных заполняются в памяти.
Правильный способ разработки функций varargs состоит в том, чтобы фактически иметь две версии, которые принимают "...", которая, в свою очередь, извлекает va_list и передает ее функции, которая принимает va_list. Таким образом, вы можете динамически строить аргументы, если вам нужно, и вместо этого можете вызывать версию функции va_list.
Большинство стандартных функций ввода-вывода имеют версии varargs: vprintf для printf, vsprintf для 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). Поэтому используйте этот метод на свой страх и риск и проверьте, проверьте, проверьте).
Нет портативного способа создания списка аргументов для функции переменных аргументов в 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)
Или, если число аргументов фиксировано для каждого конкретного вызова, напишите script, чтобы сгенерировать препроцессор, чтобы скрыть, насколько это отвратительно этот вызов.
#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 */
Обратите внимание, что введенный образец кода имеет поведение undefined - запятые, что отдельные параметры функции не являются точками последовательности (эти запятые не являются оператором запятой), поэтому несколько раз изменяя i
и или j
в список аргументов вызова функции приводит к поведению undefined.
Это не означает, что порядок оценки аргументов вызова функции не указан стандартом - так что даже если вы сделали модификацию i
и j
, используя функции для оценки аргументов (сами вызовы функций - это последовательность точки), вы бы довольно много передавали указатели в неопределенном порядке.
Кроме того, я не вижу, как web_submit_data()
знает, сколько аргументов было передано - я не вижу в конце счетчика или окончательного аргумента дозорного. Но я думаю, что ваш пример может быть именно таким - пример, который может не иметь полных, точных деталей. С другой стороны, проблема web_submit_data()
в любом случае, верно?
Есть два способа передать переменное количество аргументов: функции, которая принимает "..." или функции, которая принимает va_list.
Вы не можете динамически определять количество аргументов для интерфейса "...", но вы должны иметь возможность сделать это для va_list. Google для va_start, va_end и va_list.
Я знаю, что это старый поток, но я просто наткнулся на него. Правильный способ обработки данных формы переменной длины в LoadRunner заключается в использовании web_custom_request(). Вы создаете структуру пары name | value для переменной длины аргументов как строку и передаете ее как часть функции.
Запишите один вызов как web_custom_request(), и структура строки аргумента для пар name | value станет очевидной. Просто используйте любые функции обработки строки C, которые вы хотите построить, и укажите ее как часть списка аргументов для web_custom_request().