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

q。图书馆有没有办法知道如何迭代并浏览成员字段,看看是否有指向此类功能的指针并称其为可用。

图书馆有办法知道如何迭代并通过成员字段,看看是否有指向此类功能并将其称为可用的指针。

没有。

最好的选择是在具有这些成员的库中创建一个结构,并通过该结构而不是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 An");
}
void reg_b()
{
    printf("reg of Bn");
}
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(BTW。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=%dn", 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, %sn", 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说:

男人stdarg

va_start()

[...]

因为该参数的地址可以在va_start()宏中使用,所以 它不应将其声明为寄存器变量,也不应将其声明为 功能或数组类型。

您还可以采用类似于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 *))

此函数采用一个空隙指针指向您的结构,然后在需要注册或删除该结构时可以调用两个功能。不需要将功能作为结构的成员,我通常不建议它。我建议在Qsort上阅读,因为它做的事情与您要做的类似。

最新更新