Llamar dinámicamente a una función C con un argumento varargs

9 minutos de lectura

avatar de usuario
Randakar

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

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

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


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 no a) ser completamente dependiente del procesador yb) intentar enseñar a Loadrunner sobre la vinculación en fuentes externas.

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

Además: no tengo control sobre la implementación de web_submit_data(). Entonces, simplemente empujar el problema hacia abajo un nivel no va a resolverlo.

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 llamadas lo hace …

  • El código está roto: múltiples modificaciones de i y j entre puntos de secuencia.

    – MSalters

    12 de noviembre de 2008 a las 9:39

  • Heh, wow, esto es algo viejo, viejo.

    – Randakar

    15 de octubre de 2020 a las 12:03

  • Para muchos de los que intentan responder a esto, tenga en cuenta que Loadrunner (en ese momento) no admitía muchas de las sutilezas del estándar C a las que la gente podría estar acostumbrada. En particular: No hay bibliotecas que no sean las proporcionadas por HP. Poco o ningún soporte para macros, no estoy seguro de que tenga un preprocesador. ¿Definiciones de tipos? Probablemente no. Etcétera, etcétera.

    – Randakar

    15 oct 2020 a las 12:05

En CamelBones uso libffi para llamar a objc_msgSend(), que es una función varargs. Funciona de maravilla.

  • Esta respuesta es dorada. También estoy usando para objc_msgSend().

    – cbh2000

    25 mayo 2014 a las 20:59


  • Esta es probablemente la única respuesta correcta. No existe una solución fácil e independiente de la plataforma. Así que use una biblioteca que abstraiga la plataforma. Otra biblioteca de este tipo es llamada dinámica.

    – Andreas Haferburg

    14/03/2019 a las 21:10


Los argumentos de longitud variable son básicamente solo 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 arquitectura de hacer esto es usar las macros va_list (que mencionó n-alexander), de lo contrario, puede tener problemas con la forma en que se rellenan varios tipos de datos en la memoria.

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

La mayoría de las funciones estándar de E/S tienen versiones de varargs: vprintf para printf, vsprintf para sprintf… se hace una idea. Vea si su biblioteca implementa una función llamada “vweb_submit_data” o algo por el estilo. Si no lo hacen, envíeles un correo electrónico y dígales que arreglen su biblioteca.

3000 líneas de lo mismo (incluso si es inducido por el preprocesador) me hace temblar

avatar de usuario
miguel rebabas

Ya que generalmente no es un problema pasar más argumentos a una función que toma argumentos variables de los que espera la función (consulte la nota al pie n. ° 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 n.° 1: Si piensa en cómo las macros en stdargs.h acto esto se vuelve claro. Sin embargo, no afirmo que esta técnica cumpla con los estándares. De hecho, en la historia reciente, se ha descubierto que las respuestas de stackoverflow que publiqué donde hice este descargo de responsabilidad no cumplen con los estándares (generalmente por el siempre vigilante litb). Así que use esta técnica bajo su propio riesgo y verifique, verifique, verifique).

  • compatible o no, es una técnica que he usado comúnmente (y visto usar) durante mucho tiempo.

    – dkretz

    11 de noviembre de 2008 a las 18:39

  • @ michael-burr compatible o no, agradezco su respuesta, gracias.

    –Michael Robinson

    23 de mayo de 2012 a las 12:11

No existe 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 dependientes de la implementación trucos por ahí, la biblioteca de dyncall que encontró parece buena y probablemente más portátil que la mayoría.

Nota: el código ya depende del compilador (aunque quizás no del procesador), porque la invocación de web_submit_data asume que las subexpresiones de argumentos en una llamada a procedimiento se evalúan de izquierda a derecha, pero el lenguaje C deja sin especificar el orden de evaluación de argumentos.

Ver para referencia: http://en.wikipedia.org/wiki/Estrategia_de_Evaluación#Call_by_value

Entonces, quizás la solución no portátil no empeore significativamente las cosas para usted.

  • Es peor que depende de la implementación: no está definido porque i y j se modifican varias veces sin un punto de secuencia intermedio.

    – Michael Burr

    11 de noviembre de 2008 a las 18:38

avatar de usuario
ben collins

¿Puedes reestructurar tu código para que esto no sea necesario? Tal vez podría 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 desarrollado: mi esposa me llamó para desayunar. 🙂

  • Es peor que depende de la implementación: no está definido porque i y j se modifican varias veces sin un punto de secuencia intermedio.

    – Michael Burr

    11 de noviembre de 2008 a las 18:38

avatar de usuario
HUAGHAGUAH

Escríbalo una vez con el preprocesador y nunca mire hacia 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 la cantidad de argumentos es fija para cada llamada específica, escriba un script 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 */

  • Comportamiento indefinido: i++ aparece varias veces entre puntos de secuencia. Como en la pregunta.

    –Jonathan Leffler

    12 de noviembre de 2008 a las 22:15

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad