该解决方案由两个部分组成,一个是一个静态库,从库的用户接收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个有关可能的结构类型的信息,则您无法做到。图书馆必须以某种方式获得信息或偏移。
我能想到的唯一方法是:
- 所有
register
成员都有相同的原型 - 将偏置传递到功能。
我创建了一个
的示例#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上阅读,因为它做的事情与您要做的类似。