简体   繁体   English

通过void访问结构的成员*

[英]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. 该解决方案包括两个部分,一个是静态库,该静态库从库的用户那里接收struct的实例。 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*接收

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* . 最好的选择是在具有这些成员的库中创建一个结构,然后传递该结构而不是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. 正如@immibis所说,如果编译器不知道传递给函数的数据类型是什么,则无法进行此工作(即编译器无法证明编译此类代码的合理性)。

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: 由于您希望将对象传递到库而不在库中存储有关每个对象的类型的信息,因此可以通过执行以下操作在C中伪造多态

callback.h callback.h

#ifndef _CALLBACK_H_
#define _CALLBACK_H_

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

#endif _CALLBACK_H_

pre_comp.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 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 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 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. 如果库中有关于可能的结构类型的0信息,则您将无法执行。 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 所有register成员都具有相同的原型
  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. 我使用valgrind运行它,但未收到任何错误或警告。 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. 我不确定这是否由于uintptr_t*转换而违反了某种严格的别名规则或是否产生未定义的行为,但至少看来可行。

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: 但是,我认为,更干净的解决方案是重写register (顺便说一下, register是C中的关键字,您不能将其用作函数名称)函数以接受函数指针和可能的参数,如下所示:

#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. 注意reg有一个dummy参数。 I do that because the man page of stdarg says: 我这样做是因为stdarg的手册页说:

man stdarg 曼达达

va_start() : 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. 由于此参数的地址可以在va_start()宏中使用, 因此不应将其声明为寄存器变量,函数或数组类型。

You can take an approach similar to qsort and pass function pointers in addition to a void pointer to the structure. 除了指向该结构的void指针之外,您还可以采用类似于qsort并传递函数指针的方法。

Here is the function prototype for qsort, which is a function that can be used to sort arrays of any type: 这是qsort的函数原型,该函数可用于对任何类型的数组进行排序:

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. 它需要一个执行比较的函数指针,因为没有它,qsort不会知道如何比较两个对象。

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. 该函数将void指针指向您的结构,然后在需要注册或删除该结构时可以调用两个函数。 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. 我建议您阅读qsort,因为它的功能与您尝试执行的操作类似。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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