首先,我了解如何使用函数指针和字符串或其他查找来实现调度表,这不是挑战。
我正在寻找的是某种在编译时向该表动态添加条目的方法。
我希望的代码结构类型是这样的:
Strategy.h - 包含调度程序和调度表定义的函数定义 Strategy.c - 包含调度程序的代码
MyFirstStrategy.c - 包括 Strategy.h 并提供该策略的一个实现 MyOtherStrategy.c - 包含 Strategy.h 并提供策略的第二个实现
这个想法是,将函数指针和策略名称插入调度表的代码不应该存在于 Strategy.c 中,而应该存在于单独的策略实现文件中,并且查找表应该在编译时以某种方式动态构建。
对于固定大小的调度表,这可以按如下方式进行管理,但我想要一个动态大小的表,我不希望 Strategy.c 实现必须包含实现的所有头文件,我会就像要在编译时而不是运行时构建的调度表。
固定大小示例
策略.h
typedef void strategy_fn_t(int);
typedef struct {
char *strategyName;
strategy_fn_t *implementation;
} dispatchTableEntry_t;
MyFirstStrategy.h
#include "Strategy.h"
void firstStrategy( int param );
MyOtherStrategy.h
#include "Strategy.h"
void otherStrategy( int param );
策略.c
#include "Strategy.h"
#include "MyFirstStrategy.h"
#include "MyOtherStrategy.h"
dispatchTableEntry_t dispatchTable[] = {
{ "First Strategy", firstStrategy },
{ "Other Strategy", otherStrategy }
};
int numStrategies = sizeof( dispatchTable ) / sizeof(dispatchTable[0] );
我真正想要的是一些预处理器魔术,我可以将其插入到策略实现文件中以自动处理,例如
MyFirstStrategy.c
#include "Strategy.h"
void firstStrategy( int param );
ADD_TO_DISPATCH_TABLE( "First Strategy", firstStrategy );
有什么想法吗?
最佳答案
在具有 gnu 链接器和编译器或兼容的东西的系统上,可以将某些对象放在不同的部分中。然后链接器将为该部分的开始和结束生成符号。使用它,您可以将所有结构放入不同对象的该部分,链接器将在链接时合并这些部分,您可以将它们作为数组访问。如果您在共享库中执行此操作,则需要进行更多操作,并且绝对不能在 ELF Linux/*BSD 之外移植。
我在 MacOS 和 a.out BSD 上做过类似的事情(虽然这个例子不会工作),但我丢失了那个代码。以下是如何在 Linux 上运行的示例:
$ cat link_set.c
#include <stdio.h>
struct dispatch_table {
const char *name;
void (*fun)(int);
};
#define ADD_TO_DISPATCH_TABLE(name, fun) \
static const struct dispatch_table entry_##fun \
__attribute__((__section__("link_set_dispatch_table"))) = \
{ name, fun }
int
main(int argc, char **argv)
{
extern struct dispatch_table __start_link_set_dispatch_table;
extern struct dispatch_table __stop_link_set_dispatch_table;
struct dispatch_table *dt;
for (dt = &__start_link_set_dispatch_table; dt != &__stop_link_set_dispatch_table; dt++) {
printf("name: %s\n", dt->name);
(*dt->fun)(0);
}
return 0;
}
void
fun1(int x)
{
printf("fun1 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 1", fun1);
void
fun2(int x)
{
printf("fun2 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 2", fun2);
$ cc -o link_set link_set.c
$ ./link_set
name: fun 1
fun1 called
name: fun 2
fun2 called
$
解释宏的作用。它创建了一个 struct dispatch_table ,我们希望它的名字是唯一的,因为你可能想在一个对象中多次使用它(如果你多次使用同一个函数,想出一些其他的方式来命名结构)并用gnu 扩展来指定对象应该结束的部分。如果我们将对象放入“some_section_name”,那么链接器将自动添加符号 __start_some_section_name 和 __end_some_section_name。然后我们可以在这些符号之间移动并获取我们放入该部分的所有结构。
更新了 MacOS 的工作示例:
#include <stdio.h>
#include <mach-o/ldsyms.h>
#include <mach-o/getsect.h>
#include <mach-o/loader.h>
struct dispatch_table {
const char *name;
void (*fun)(int);
};
#define ADD_TO_DISPATCH_TABLE(name, fun) \
static const struct dispatch_table entry_##fun \
__attribute__((__section__("__DATA,set_dt"))) = \
{ name, fun }
int
main(int argc, char **argv)
{
struct dispatch_table *start;
unsigned long sz;
intptr_t s;
int i;
s = (intptr_t)getsectdata("__DATA", "set_dt", &sz);
if (s == 0)
return 1;
s += _dyld_get_image_vmaddr_slide(0);
start = (struct dispatch_table *)s;
sz /= sizeof(*start);
for (i = 0; i < sz; i++) {
struct dispatch_table *dt = &start[i];
printf("name: %s\n", dt->name);
(*dt->fun)(0);
}
return 0;
}
void
fun1(int x)
{
printf("fun1 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 1", fun1);
void
fun2(int x)
{
printf("fun2 called\n");
}
ADD_TO_DISPATCH_TABLE("fun 2", fun2);
这里的部分必须被称为“set_dt”,因为在这种可执行格式中部分名称的长度是有限的。
当然,如果你已经达到需要这个的程度,你肯定明白这一切都非常危险,不可移植,不能保证永远工作(我三年前的代码在当前版本上不起作用macos),没有类型或其他安全性,如果其他东西决定将东西放入具有相同名称的部分,那么东西最终会变成非常漂亮的烟花。但这是一个非常巧妙的技巧。我在两个大型项目中使用了这种方法,它确实节省了很多工作中央调度表不必在共享存储库中编辑,这曾经给每个人带来冲突。
关于c - 如何在 C 中实现动态调度表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11840651/