简体   繁体   中英

Accessing members of the struct via void *

The solution consists of two parts, one is a static library that receives instances of struct from the user of the library. Library doesn't know what will be the type of structs, all it knows there will be two function pointers to it with a specific name.

Library Code

pre-compiled library has no way of knowing types of user structs, hence receiving via 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
}

User of the Library Code

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

Q. Is there a way for the Library to know how to iterate and go through member fields and see if there's a pointer to such function and call it available.

Is there a way for the Library to know how to iterate and go through member fields and see if there's a pointer to such function and call it available.

No there is not.

Your best bet is to create a structure in the library that has these members, and pass that structure instead of void* .

As @immibis said, there is no way for this to work (ie no way for the compiler to justify compiling such code) if the compiler does not know what the types of the data being passed to the function are.

Since you wanted to pass the objects along to the library without storing information about the type of each object in the library, you can fake polymorphism in C , by doing the following:

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

main.c

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

compiling

gcc pre_comp.c main.c

output

 registered removed 

If the library has 0 information about the possible struct types, then you cannot do it. The library has to get somehow the information or the offsets.

The only way I can think of is:

  1. All register member have the same prototype
  2. Pass the offset to the function.

I created an example of this

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

This prints:

$ ./c 
reg of A
reg of B

I run it with valgrind and I did not get any errors nor warnings. I'm not sure if this violates somehow strict aliasing rules or yields undefined behaviour because of the uintptr_t* conversions, but at least it seems to work.

I think however, the more cleaner solution is to rewrite the register (btw. register is a keyword in C, you cannot use that for a function name) function to accept a function pointer and possible parameters, something like this:

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

This has the output:

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

Note that reg has a dummy parameter. I do that because the man page of stdarg says:

man stdarg

va_start() :

 [...] 

Because the address of this argument may be used in the va_start() macro, it should not be declared as a register variable, or as a function or an array type.

You can take an approach similar to qsort and pass function pointers in addition to a void pointer to the structure.

Here is the function prototype for qsort, which is a function that can be used to sort arrays of any type:

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

It takes a function pointer that performs the comparison because without it qsort wouldn't know how to compare two objects.

This can be applied to your task with a function prototype like this:

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

This function takes a void pointer to your struct and then two functions that it can call when it needs to register or remove that struct. Having the functions be members of the struct is not required and I generally do not recommend it. I recommend reading up on qsort because it is does something similar to what you are trying to do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM