6

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

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

我希望的代码结构类型类似于:

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

有什么想法吗 ?

4

3 回答 3

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
$

解释宏的作用。它创建了一个结构 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 上不起作用) , 没有类型或其他安全性,如果其他东西决定把东西放在同名的部分,事情最终会变成非常漂亮的烟花。但这是一个非常巧妙的技巧。我在两个大型项目中使用了这种方法,它确实节省了大量工作,中央调度表不必在曾经给每个人带来冲突的共享存储库中编辑。

于 2012-08-07T10:55:59.703 回答
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 结构,但我认为这不是主要问题。

一如既往,警告购买者,因为我没有编译/运行这段代码,只是为了说明技术(我在各种工作项目中使用过几次)。

于 2012-08-07T07:07:27.827 回答
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 }

策略.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 编译器和平台,而无需任何链接时间技巧。

于 2012-08-09T11:48:35.313 回答