繁体   English   中英

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

[英]How can I implement a dynamic dispatch table in C

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

我正在寻找的是在编译时动态地向该表添加条目的一些方法。

我希望的代码结构类型是这样的:

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

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

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

对于固定大小的调度表,这可以如下管理,但我想要一个动态大小的表,我不希望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 );

有什么想法吗 ?

在具有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上不起作用) ,没有任何类型或其他安全性,如果其他东西决定将东西放入一个具有相同名称的部分,那么事情最终会变得非常漂亮。 但这是一个非常巧妙的伎俩。 我在两个大型项目中使用此方法,它确实节省了大量工作,中央调度表不必在共享存储库中进行编辑,该共享存储库曾经给每个人带来冲突。

您应该能够使用具有函数指针的结构的链表来执行此操作:

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结构,但我不认为这是一个主要问题。

和往常一样,我没有编译/运行这段代码,并且只是为了说明这种技术(我已经在各种工作项目中使用了几次)。

由于您的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编译器和平台,没有任何链接时间技巧。

暂无
暂无

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

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