Llamando una función de C con un argumento varargs dinámicamente

Estoy progtwigndo en C contra una biblioteca de terceros (en HP / Mercury Loadrunner) que permite una lista de argumentos de tamaño variable de varargs para una de sus funciones. Quiero llamar a esta función, pero no sé de antemano cuántos argumentos tendré.

Hay una función realizada por uno de mis predecesores que sirve un poco, pero el problema aquí es que esta función asume el peor escenario (más de 3000 argumentos) y códigos de mano para eso.

Para iluminar, aquí está el (principio de) el código. La función que llamamos es web_submit_data() . Se HTTP publicará un conjunto de datos de formulario. Esta implementación se produjo cuando se trata de formularios generados dinámicamente con un número arbitrario de campos. (Se limpió un poco del original, que también se codificó manualmente a mano índices …)


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

Ahora he encontrado una biblioteca externa que podría funcionar ( http://www.dyncall.org ) pero preferiría que no sea totalmente dependiente del procesador yb) intente enseñarle a Loadrunner cómo vincular fonts externas.

Edición: la función original usaba índices codificados en vez de usar una variable. Todavía puede volver a eso si resulta ser demasiado impredecible. Sin embargo, como es poco probable que lo ejecute con un comstackdor o un sistema operativo / hardware diferente, dudo que realmente valga la pena.

Además: no tengo control sobre la implementación de web_submit_data (). Así que solo empujar el problema hacia abajo en un nivel no va a cortarlo …

Otra cosa a tener en cuenta: la especificación para web_submit_data() usa una constante llamada LAST para marcar el final de la lista de argumentos. La implementación original no lo usa. Presumiblemente el sitio de llamada hace …

En CamelBones utilizo libffi para llamar a objc_msgSend (), que es una función de varargs. Trabaja un capricho.

Los argumentos de longitud variable son básicamente un puntero a un montón de datos empaquetados que se pasan a la función requerida. Es responsabilidad de la función llamada interpretar estos datos empaquetados.

La forma segura de hacer esto con la architecture es utilizar las macros va_list (que n-alexander mencionó), de lo contrario, podría tener problemas con la forma en que se rellenan los distintos tipos de datos en la memoria.

La forma correcta de diseñar funciones de varargs es tener realmente dos versiones, una que acepte el ‘…’, que a su vez extrae la lista_vista y la pasa a una función que toma una lista_varia. De esta manera, puede construir dinámicamente los argumentos si es necesario y puede llamar a la versión va_list de la función.

La mayoría de las funciones de IO estándar tienen versiones de varargs: vprintf para printf, vsprintf para sprintf … entiendes la idea. Vea si su biblioteca implementa una función llamada “vweb_submit_data” o algo parecido. Si no lo hacen, envíeles un correo electrónico y dígales que reparen su biblioteca.

3000 líneas de la misma cosa (incluso si es inducida por preprocesador) me hacen temblar

Como generalmente no es un problema pasar más argumentos a una función que toma argumentos variables de lo que la función espera (vea la nota al pie de página # 1), puede hacer algo como lo siguiente:

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

Nota al pie # 1: si piensa en cómo actúan las macros en stdargs.h esto queda claro. Sin embargo, no pretendo que esta técnica cumpla con los estándares. De hecho, en la historia reciente, las respuestas de stackoverflow que he publicado donde he hecho este descargo de responsabilidad han sido de hecho que no cumplen con los estándares (generalmente por parte de la litb siempre vigilante). Entonces, utilice esta técnica bajo su propio riesgo, y verifique, verifique, verifique).

No hay una forma portátil de crear una lista de argumentos para una función de argumento variable en C en tiempo de ejecución. Hay algunos trucos dependientes de la implementación, la biblioteca de dyncall que encontraste se ve como una buena y probablemente más portátil que la mayoría.

Nota: el código ya es dependiente del comstackdor (aunque quizás no dependa del procesador), porque la invocación de web_submit_data supone que las subexpresiones de argumentos en una llamada de procedimiento se evalúan de izquierda a derecha, pero el lenguaje C deja la orden de la evaluación del argumento no especificado.

Ver para referencia: http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_value

Entonces, tal vez la solución no portátil no va a empeorar las cosas significativamente para usted.

¿Puedes reestructurar tu código para que esto no sea necesario? Quizás podrías tomar el búfer entrante y hacerlo más determinista:

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

Lo siento, esto no podría ser más completo, mi esposa me llamó para desayunar. 🙂

Escríbelo una vez con el preprocesador y nunca mires atrás.

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

O si el número de argumentos es fijo para cada llamada específica, escriba una secuencia de comandos para generar definiciones de preprocesador para ocultar cuán atroz es esa llamada.

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

Tenga en cuenta que el ejemplo de código que publicó tiene un comportamiento indefinido: las comas que separan los parámetros de una función no son puntos de secuencia (esas comas no son el operador de coma), por lo que modificar i j varias veces en la lista de argumentos de la función llama a un comportamiento indefinido.

Esto no quiere decir que el orden de evaluación de los argumentos de llamada a función no esté especificado por el estándar, por lo que incluso si realizó la modificación de i y j usando funciones para evaluar los argumentos (las llamadas a funciones en sí mismas son puntos de secuencia), sería bastante Pasan mucho los punteros en un orden indeterminado.

Además, no veo cómo web_submit_data() sabe cuántos argumentos se han pasado; no veo un recuento o un argumento centinela definitivo al final. Pero supongo que su ejemplo puede ser solo eso, un ejemplo que puede no tener detalles completos y precisos. Por otro lado, es el web_submit_data() de web_submit_data() todos modos, ¿verdad?

Hay dos formas de pasar un número variable de argumentos: a una función que acepta “…” oa una función que acepta va_list.

No puede definir dinámicamente el número de argumentos para la interfaz “…”, pero debería poder hacerlo para la lista va_list. Google para va_start, va_end y va_list.

Sé que este es un hilo viejo, pero simplemente lo encontré. La forma correcta de manejar datos de formulario de envío de longitud variable en LoadRunner es usar un web_custom_request (). Usted construye la estructura de pares nombre | valor para la longitud variable de los argumentos como una cadena y la pasa como parte de la función.

Registre la llamada como web_custom_request () y la estructura de la cadena de argumento para los pares nombre | valor será evidente. Simplemente use cualquier función de manejo de cadenas C que desee para construir la cadena en cuestión e inclúyala como parte de la lista de argumentos para web_custom_request ().