La solución consta de dos partes, una es una biblioteca estática que recibe instancias de estructura del usuario de la biblioteca. Library no sabe cuál será el tipo de estructuras, todo lo que sabe es que habrá dos punteros de función con un nombre específico.

Código de biblioteca

la biblioteca precompilada no tiene forma de conocer los tipos de estructuras de usuario, por lo tanto, recibe a través de void*

void save(void *data) {
    // library will save/cache user's object
    data->registered(); // if register successful
}

void remove(void *data) {
    // library will remove the object from memory
    data->remove(); // if removed successful
}

Usuario del código de la biblioteca

struct Temp { // random order of fields
   void (*custom1)();
   void (*registered)();
   void (*custom2)();
   void (*remove)();
   void (*custom3)();
}

void reg() {
    printf("registered");
}

void rem() {
    printf("removed");
}

void custom1() {}
void custom2() {}
void custom3() {}

var temp = malloc(struct Temp, sizeof(struct Temp));
temp->registered = reg;
temp->remove = rem;
temp->custom1 = custom1; // some custom functions
temp->custom2 = custom2; 
temp->custom3 = custom3;


// calling library code
save(temp);
remove(temp);

P. ¿Hay alguna manera para que la Biblioteca sepa cómo iterar y recorrer los campos de miembros y ver si hay un puntero a dicha función y llamarla disponible?

c
0
App2015 26 feb. 2018 a las 02:00

4 respuestas

La mejor respuesta

Como dijo @immibis, no hay forma de que esto funcione (es decir, no hay forma de que el compilador justifique la compilación de dicho código) si el compilador no sabe cuáles son los tipos de datos que se pasan a la función.

Como deseaba pasar los objetos a la biblioteca sin almacenar información sobre el tipo de cada objeto en la biblioteca, puede polimorfismo falso en C, haciendo lo siguiente:

Callback.h

#ifndef _CALLBACK_H_
#define _CALLBACK_H_

typedef struct {
    void (*registered)();
    void (*removed)();
} ICallback;

#endif _CALLBACK_H_

Pre_comp.h

#ifndef _PRE_COMP_H_
#define _PRE_COMP_H_

#include "callback.h"

void save(ICallback* data);
void remove(ICallback* data);

#endif /* _PRE_COMP_H_ */

Precomp.c

#include <stdlib.h> /* NULL */

#include "callback.h"
#include "pre_comp.h"

void save(ICallback *data) {
    if (NULL != data && NULL != data->registered) {
        data->registered(); // if register successful
    }
}

void remove(ICallback *data) {
    if (NULL != data && NULL != data->removed) {
        data->removed(); // if removed successful
    }
}

C Principal

#include <stdio.h>

#include "pre_comp.h"
#include "callback.h"

struct Temp {
    ICallback base; // has to be defined first for this to work
    void (*custom1)();
    void (*custom2)();
    void (*custom3)();
};


// calling library code

void reg() {
    puts("registered");
}

void rem() {
    puts("removed");
}


int main() {
    struct Temp data = {{reg, rem}};
    save((ICallback*)&data);
    remove((ICallback*)&data);
}

Compilando

gcc pre_comp.c main.c

Salida

registered
removed
1
smac89 26 feb. 2018 a las 04:20

¿Hay alguna manera para que la Biblioteca sepa cómo iterar y recorrer los campos de miembros y ver si hay un puntero a dicha función y llamarla disponible?

No no hay.

Su mejor opción es crear una estructura en la biblioteca que tenga estos miembros y pasar esa estructura en lugar de void*.

2
user253751 25 feb. 2018 a las 23:12

Si la biblioteca tiene 0 información sobre los posibles tipos de estructura, entonces no puede hacerlo. La biblioteca tiene que obtener de alguna manera la información o las compensaciones.

La única forma en que puedo pensar es:

  1. Todos los miembros register tienen el mismo prototipo
  2. Pase el desplazamiento a la función.

Creé un ejemplo de esto

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

// function that does not know anything about any struct
void reg(void *data, size_t offset)
{
    uintptr_t *p = (uintptr_t*) (((char*) data) + offset);

    void (*reg)() = (void(*)()) *p;

    reg();
}


struct A {
    int c;
    void (*reg)();
};

struct B {
    int b;
    int c;
    void (*reg)();
};

void reg_a()
{
    printf("reg of A\n");
}

void reg_b()
{
    printf("reg of B\n");
}

int main(void)
{
    struct A a;
    struct B b;

    a.reg = reg_a;
    b.reg = reg_b;


    reg(&a, offsetof(struct A, reg));
    reg(&b, offsetof(struct B, reg));
    return 0;
}

Esto imprime:

$ ./c 
reg of A
reg of B

Lo ejecuté con valgrind y no recibí ningún error ni advertencia. No estoy seguro si esto viola de alguna manera las reglas estrictas de alias o produce un comportamiento indefinido debido a las conversiones uintptr_t*, pero al menos parece funcionar.

Sin embargo, creo que la solución más limpia es reescribir el register (por cierto register es una palabra clave en C, no se puede usar para una función) aceptar un puntero de función y posibles parámetros, algo como esto:

#include <stdio.h>
#include <stdarg.h>

void reg(void (*func)(va_list), int dummy, ...)
{
    if(func == NULL)
        return;

    va_list ap;
    va_start(ap, dummy);
    func(ap);
    va_end(ap);
}


void reg1(int a, int b)
{
    printf("reg1, a=%d, b=%d\n", a, b);
}

void vreg1(va_list ap)
{
    int a = va_arg(ap, int);
    int b = va_arg(ap, int);
    reg1(a, b);
}

void reg2(const char *text)
{
    printf("reg2, %s\n", text);
}

void vreg2(va_list ap)
{
    const char *text = va_arg(ap, const char*);
    reg2(text);
}

int main(void)
{
    reg(vreg1, 0, 3, 4);
    reg(vreg2, 0, "Hello world");
    return 0;
}

Esto tiene la salida:

reg1, a=3, b=4
reg2, Hello world

Tenga en cuenta que reg tiene un parámetro dummy. Lo hago porque la página de manual de stdarg dice:

man stdarg

va_start():

[...]

Como la dirección de este argumento puede usarse en la macro va_start(), no debe declararse como una variable de registro o como función o un tipo de matriz.

1
Pablo 26 feb. 2018 a las 00:01

Puede adoptar un enfoque similar a qsort y pasar punteros de función además de un puntero vacío a la estructura.

Aquí está el prototipo de función para qsort, que es una función que se puede usar para ordenar matrices de cualquier tipo:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

Se necesita un puntero de función que realice la comparación porque sin él qsort no sabría cómo comparar dos objetos.

Esto se puede aplicar a su tarea con un prototipo de función como este:

int DoFoo(void *thing, void (*register)(void *), void (*remove)(void *))

Esta función toma un puntero nulo a su estructura y luego dos funciones a las que puede llamar cuando necesita registrar o eliminar esa estructura. No es necesario que las funciones sean miembros de la estructura y generalmente no lo recomiendo. Recomiendo leer sobre qsort porque hace algo similar a lo que está tratando de hacer.

0
afic 25 feb. 2018 a las 23:28