如何在C中实现动态调度表

时间:2012-08-07 06:47:33

标签: c c-preprocessor dispatch

首先,我了解如何使用函数指针和字符串或其他查找来实现调度表,这不是挑战。

我正在寻找的是在编译时动态地将条目添加到此表的方法

我希望的代码结构类型如下:

Strategy.h - 包含调度程序和调度表定义的函数定义 Strategy.c - 包含调度程序的代码

MyFirstStrategy.c - 包括Strategy.h并提供该策略的一个实现 MyOtherStrategy.c - 包括Strategy.h并提供策略的第二个实现

这个想法是将函数指针和策略名称插入到调度表中的代码不应该存在于Strategy.c中,而应该存在于各个策略实现文件中,并且查找表应该以某种方式在编译时动态构造。 / p>

对于固定大小的调度表,这可以如下管理,但我想要一个动态大小的表,我不希望Strategy.c实现必须包含实现的所有头文件,我想比如在编译时构建的调度表,而不是运行时。

固定大小示例

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

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

有什么想法吗?

3 个答案:

答案 0 :(得分:4)

在具有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”,因为此可执行格式的部分名称的长度有限。

当然,如果你已经满足了这一点,你肯定会明白这一切都非常危险,不可移植,无法保证能够正常工作(三年前我的代码在当前版本中无效)没有类型或其他安全性,如果其他东西决定将东西放入一个具有相同名称的部分,那么事情最终会变得非常漂亮。但这是一个非常巧妙的伎俩。我在两个大型项目中使用此方法,它确实节省了大量工作,中央调度表不必在共享存储库中进行编辑,而共享存储库曾经给每个人带来冲突。

答案 1 :(得分:1)

你应该可以使用带有函数指针的结构列表的链表:

struct dispatch_entry {
    const char *name;
    void (*func)(int);
    struct dispatch_entry *next;
};

struct dispatch_entry *dispatch_head = NULL;

#define ADD_TO_DISPATCH_TABLE(entry) do { \
    (entry)->next = dispatch_head; \
    dispatch_head = entry; \
} while (0)

然后,您可以遍历列表以查找所需的条目,或者稍后在运行时将其排序/放入优化的查找例程等。请注意,这需要您在宏之外实例化dispatch_entry结构,但我不认为这是一个主要问题。

与往常一样,请注意,因为我没有编译/运行此代码,并且只是为了说明这种技术(我在各种工作项目中使用了几次)而打了一针。

答案 2 :(得分:1)

由于您的Strategy.c显然已经知道了名称(“#include”XYstrategy.h“)的策略实例,您可以全力以赴,使用头文件而不是实现文件将您的策略​​传达给中央调度器:

MyFirstStrategy.h

#include "Strategy.h"
void firstStrategy( int param );
#define MY_FIRST_STRATEGY {"First Strategy", firstStrategy}

MyOtherStrategy.h

#include "Strategy.h"
void otherStrategy( int param );
#define MY_OTHER_STRATEGY {"Other Strategy", otherStrategy }

Strategy.c

#include "Strategy.h"
#include "MyFirstStrategy.h"
#include "MyOtherStrategy.h"

dispatchTableEntry_t dispatchTable[] = {
    MY_FIRST_STRATEGY,
    MY_OTHER_STRATEGY
};
int numStrategies = sizeof( dispatchTable ) / sizeof(dispatchTable[0] );

这适用于任何C编译器和平台,没有任何链接时间技巧。