En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

sábado, 18 de mayo de 2013

La maldición de la Callback de jade II – El retorno
cómo escribir una Callback en C - pt.2

He decidido hacer una pausa en el tema del C a objetos: ya he escrito dos post sobre el tema y no quiero aburrirme ni aburriros. Lo sé, se podría profundizar más detalles y hablar (como prometido) de C vs C + +, pero, en este momento, no me da la gana. Sin embargo, tarde o temprano, volveremos seguro en eso...

Así que, hoy, volvemos a un tema ya tratado aquí, y sobre el cual (me dí cuenta ayer) se podría añadir algo interesante. Estoy hablando, otra vez, de las funciones callback.

Por supuesto, antes de seguir leyendo, debéis refrescaros la memoria releyendo el otro post sobre el tema, ya que es una extensión de esto, y están estrechamente vinculados.

Pausa para la reflexión...

¿Ya estáis de vuelta? ¿pero cómo? Todavía no habéis releído el antiguo post? Hay que leerlo, gracias...

Otra pausa para la reflexión...

Bueno, ahora podemos continuar.

Así que, cómo habréis re-notado, el ejemplo que yo había propuesto era, yo diría, clásico, inspirado en el uso de qsort(), entonces con una callback que se llama sin argumentos, pero que, en realidad, necesita dos que se generaran internamente por la misma función (la mysort() del ejemplo, o la qsort(), si prefereis).

Entonces, resumamos el flujo del ejemplo: he escrito una función main() que utiliza mysort() que, para que funcione, necesita una función para comparar dos valores. He escrito también, por lo tanto, la función de comparación (que puede ser tan simple como la del ejemplo, pero también mucho más compleja, depende de lo que se quiere lograr). La función respetaba, por supuesto, el prototipo provisto por mysort(): o sea, una función que necesita una callback, también tiene que declarar el prototipo de la callback misma (y si no ¿como la escribimos?). la mysort() misma se ocupa, pues, de llenar los dos parámetros de la callback con los valores a comparar.

Y ahora llegamos a la parte nueva: siempre refiriéndose al ejemplo de mysort() (que, imagino, ya conocéis de memoria) supongamos que necesitamos pasar otro parámetro a la callback, un parámetro externo disponible solo a nivel de la llamada principal, y que la mysort() no puede generar internamente.

¿Cómo podemos hacerlo? Veamos ahora el nuevo código (presentado como un solo bloque, pero, en realidad, a dividir en tres file):
/* parte que tendría que estar en el file mysort.h
*/
// prototipos para mySort()
typedef int (*mycallback)(int, int, void *);
void mySort(int *array, int nelems, mycallback cmpFunc, void *fp);

/* parte que tendría que estar en el file mysort.c
 */
// mySort() - funzione di sort che usa l'algoritmo bubblesort
void mySort(int *array, int nelems, mycallback cmpFunc, void *fp)
{
    // loop sobre todos los elementos de array
    while (nelems > 0) {
        // loop interno con decremento longitud
        int i;
        for (i = 0; i < (nelems - 1); i++) {
            // eseguo callback di comparazione
            if (cmpFunc(array[i], array[i + 1], fp)) {
                // eseguo swap di array[i] e array[i+1]
                int temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }
        }

        // decremento nelems
        nelems--;
    }
}

/* parte que tendría que estar en el file mymain.c
*/
// cbCmpFunc() - función de comparación
static int cbCmpFunc(int elem_a, int elem_b, void *fp)
{
    // escribo resultados parciales en un file
    fprintf(fp, "%d > %d = %d\n", elem_a, elem_b, elem_a > elem_b);

    return elem_a > elem_b;
}

// main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);
    FILE *fp = fopen("result.txt", "w");

    // ejecuto sort array
    mySort(array, nelems, cbCmpFunc, fp);

    // cierro file
    fclose(fp);

    // enseño resultados
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);
    printf("\n");

    // esco
    return 0;
}
Como se puede ver es muy similar al ejemplo del otros post, sólo que ahora, a nivel de main(), abrimos un archivo para registrar datos de elaboración y tenemos que pasar el file descriptor a la mysort(), ya que no podemos pensar de escribir una función de librería que llevas imprimido el código el nombre del file de log: es la aplicación llamante que tiene que pasarlo.

¿Y cómo lo hacemos? Es muy simple: se añade un parámetro (del tipo oportuno) a mysort() y a la callback, y en la llamada principal (en el main(), en nuestro caso) se pasa el valor a la mysort(), que se ocupará de propagarlo hasta la callback, que es el usuario del nuevo parámetro; la mysort() no lo usa, lo transporta solamente. Con este método podemos pasar todos los parámetros que queremos: en el ejemplo he añadido uno, pero se pueden añadir al gusto.

Por supuesto, todo lo anterior es válido para las funciones que implementamos nosotros: no se puede pensar en añadir parámetros a funciones de libreria que no tenemos bajo control: por ejemplo, la qsort() sólo necesita la callback con dos parámetros, y así la tenemos que aguantar.

Alguien se puede preguntar porqué el parámetro fp es un void* y no un tipo más específico (en este caso un FILE*): lo he escrito así para demostrar que, con este método, se puede pasar cualquier valor (por ejemplo, en C++ se utiliza para pasar el puntero this): de hecho, he visto código donde se agregan void* a las callback* (en fase de proyecto) sólo para uso en el futuro, de manera de poder escribir, luego, callback muy personalizadas sin necesidad de cambiar la función base (que, como se ha dicho, sólo es transportadora de estos parámetros)

¿Qué os parece? Sí, también esta vez el tema suena un poco obvio, pero si un día os vais a pelear con las callback espero que os pueda ser útil. Yo, por ejemplo, por falta de documentación y otras razones inevitables, en su momento (hace mucho tiempo) tuve que aguantarme con sólo leer el código escrito por otros, y no sé lo que habría dado por tener a mi disposición un ejemplo tan simple como esto que acabo de proponer (pero, entonces, vivimos en una época de gran suerte... pero sólo para los informáticos. Y tampoco tanto. Pero esta es otra historia ...).

Hasta el próximo post.