繁体   English   中英

通过void访问结构的成员*

[英]Accessing members of the struct via void *

该解决方案包括两个部分,一个是静态库,该静态库从库的用户那里接收struct的实例。 库不知道结构的类型,它只知道有两个带有特定名称的函数指针。

图书馆代码

预编译的库无法知道用户结构的类型,因此通过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
}

库代码的用户

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

问:库是否有一种方法可以知道如何迭代和遍历成员字段,并查看是否有指向该函数的指针并将其调用。

库是否有办法知道如何迭代和遍历成员字段,并查看是否有指向该函数的指针并将其调用。

不,那里没有。

最好的选择是在具有这些成员的库中创建一个结构,然后传递该结构而不是void*

正如@immibis所说,如果编译器不知道传递给函数的数据类型是什么,则无法进行此工作(即编译器无法证明编译此类代码的合理性)。

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

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

编译

gcc pre_comp.c main.c

产量

 registered removed 

如果库中有关于可能的结构类型的0信息,则您将无法执行。 库必须以某种方式获取信息或偏移量。

我能想到的唯一方法是:

  1. 所有register成员都具有相同的原型
  2. 将偏移量传递给函数。

我创建了一个例子

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

打印:

$ ./c 
reg of A
reg of B

我使用valgrind运行它,但未收到任何错误或警告。 我不确定这是否由于uintptr_t*转换而违反了某种严格的别名规则或是否产生未定义的行为,但至少看来可行。

但是,我认为,更干净的解决方案是重写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;
}

输出如下:

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

注意reg有一个dummy参数。 我这样做是因为stdarg的手册页说:

曼达达

va_start()

 [...] 

由于此参数的地址可以在va_start()宏中使用, 因此不应将其声明为寄存器变量,函数或数组类型。

除了指向该结构的void指针之外,您还可以采用类似于qsort并传递函数指针的方法。

这是qsort的函数原型,该函数可用于对任何类型的数组进行排序:

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

它需要一个执行比较的函数指针,因为没有它,qsort不会知道如何比较两个对象。

可以使用如下函数原型将其应用于您的任务:

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

该函数将void指针指向您的结构,然后在需要注册或删除该结构时可以调用两个函数。 不需要使函数成为该结构的成员,我通常不建议这样做。 我建议您阅读qsort,因为它的功能与您尝试执行的操作类似。

暂无
暂无

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

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