简体   繁体   English

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

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

First of all, I understand how to implement a dispatch table using function pointers and a string or other lookup, that's not the challenge. 首先,我了解如何使用函数指针和字符串或其他查找来实现调度表,这不是挑战。

What I'm looking for is some way to dynamically add entries to this table at compile time . 我正在寻找的是在编译时动态地向该表添加条目的一些方法。

The type of code structure I'd hope for is something like: 我希望的代码结构类型是这样的:

Strategy.h - contains function definition for the dispatcher and dispatch table definition Strategy.c - contains code for dispatcher Strategy.h - 包含调度程序和调度表定义的函数定义Strategy.c - 包含调度程序的代码

MyFirstStrategy.c - includes Strategy.h and provides one implementation of the strategy MyOtherStrategy.c - includes Strategy.h and provides a second implementation of the stratgy MyFirstStrategy.c - 包括Strategy.h并提供策略MyOtherStrategy.c的一个实现 - 包括Strategy.h并提供策略的第二个实现

The idea is that the code to insert function pointers and strategy names into the dispatch table should not live in Strategy.c but should be in the individual strategy implementation files and the lookup table should be somehow dynamically constructed at compile time. 我们的想法是,将函数指针和策略名称插入到调度表中的代码不应该存在于Strategy.c中,而应该存在于各个策略实现文件中,并且查找表应该以某种方式在编译时动态构造。

For a fixed size dispatch table, this could be managed as below but I want a dynamically sized table, I don't want the Strategy.c implementation to have to include all of the header files for the implementations and I'd like the dispatch table to be constructed at compile time, not run time. 对于固定大小的调度表,这可以如下管理,但我想要一个动态大小的表,我不希望Strategy.c实现必须包含实现的所有头文件,我想发送要在编译时构造的表,而不是运行时。

Fixed Size Example 固定大小示例

Strategy.h Strategy.h

typedef void strategy_fn_t(int);
typedef struct {
    char           *strategyName;
    strategy_fn_t  *implementation;
} dispatchTableEntry_t;

MyFirstStrategy.h MyFirstStrategy.h

#include "Strategy.h"

void firstStrategy( int param );

MyOtherStrategy.h MyOtherStrategy.h

#include "Strategy.h"

void otherStrategy( int param );

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

What I really want is some preprocessor magic which I can insert into the strategy implementation files to handle this automatically eg 我真正想要的是一些预处理器魔法,我可以将其插入到策略实现文件中以自动处理这个问题,例如

MyFirstStrategy.c MyFirstStrategy.c

#include "Strategy.h"

void firstStrategy( int param );

ADD_TO_DISPATCH_TABLE( "First Strategy", firstStrategy );

Any thoughts ? 有什么想法吗 ?

On systems with gnu linker and compiler or something compatible, it's possible to put certain objects in different sections. 在具有gnu链接器和编译器或兼容的系统上,可以将某些对象放在不同的部分中。 The linker will then generate symbols for the start and end of the section. 然后,链接器将为该节的开头和结尾生成符号。 Using that you can put all your structs into that section in different objects, the linker will consolidate those sections when linking and you can access them as an array. 使用它可以将所有结构放入不同对象的该部分,链接器将在链接时合并这些部分,您可以将它们作为数组访问。 This requires a lot more fiddling if you're doing this in shared libraries and is definitely not portable outside of ELF Linux/*BSD. 如果你在共享库中这样做,这需要更多的摆弄,并且绝对不能在ELF Linux / * BSD之外移植。

I've done a similar thing (although this example will not work) on MacOS and a.out BSD, but I've lost that code. 我在MacOS和a.out BSD上做了类似的事情(虽然这个例子不起作用),但我丢失了那段代码。 Here's an example of how to do it that works on Linux: 这是一个如何在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
$

To explain what the macro does. 解释宏的作用。 It creates a struct dispatch_table with a name that we hope is unique since you might want to use it multiple times in one object (if you use the same function multiple times, figure out some other way to name the struct) and defines it with the gnu extension to specify which section the object should end up with. 它创建了一个struct dispatch_table,其名称我们希望是唯一的,因为您可能希望在一个对象中多次使用它(如果您多次使用相同的函数,找出其他方法来命名结构)并使用gnu扩展名,指定对象应该以哪个部分结束。 If we put the objects into "some_section_name" then the linker will automatically add symbols __start_some_section_name and __end_some_section_name. 如果我们将对象放入“some_section_name”,则链接器将自动添加符号__start_some_section_name和__end_some_section_name。 We can then walk between those symbols and get all the structs we put into that section. 然后我们可以在这些符号之间走动并获得我们放入该部分的所有结构。

Updated with a working example for MacOS: 更新了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);

The section has to be called "set_dt" here because section names are limited in length in this executable format. 该部分必须在此处称为“set_dt”,因为部分名称在此可执行格式中的长度有限。

Of course, if you've come as far as needing this you surely understand that this is all very dangerous, unportable, not guaranteed to work ever (the code I had from three years ago didn't work on a current version of macos), has no type or other safety and if something else decides to put things into a section with the same name things will end up in very pretty fireworks. 当然,如果你已经到了需要这一点,你肯定明白这一切都是非常危险的,不可移植的,不能保证工作(我三年前的代码在当前版本的macos上不起作用) ,没有任何类型或其他安全性,如果其他东西决定将东西放入一个具有相同名称的部分,那么事情最终会变得非常漂亮。 But it's a very neat trick. 但这是一个非常巧妙的伎俩。 I use this method in two large projects and it really saves a lot of work central dispatch tables don't have to be edited in a shared repository which used to give everyone conflicts. 我在两个大型项目中使用此方法,它确实节省了大量工作,中央调度表不必在共享存储库中进行编辑,该共享存储库曾经给每个人带来冲突。

You should be able to do it with a linked list of function-pointer-having-structs: 您应该能够使用具有函数指针的结构的链表来执行此操作:

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)

You can then walk the list to find the entry you want, or later sort it/place it into optimized lookup routines etc. during runtime. 然后,您可以遍历列表以查找所需的条目,或者稍后在运行时将其排序/放入优化的查找例程等。 Note that this requires you to instantiate the dispatch_entry struct outside of the macro, but I don't think that's a major problem. 请注意,这需要您在宏之外实例化dispatch_entry结构,但我不认为这是一个主要问题。

As always, caveat emptor, as I haven't compiled/run this code, and punched it out only to illustrate the technique (which I've used a few times in various work projects). 和往常一样,我没有编译/运行这段代码,并且只是为了说明这种技术(我已经在各种工作项目中使用了几次)。

As your Strategy.c obviously already knows about the strategy instances by name ("#include "XYstrategy.h") you could go the whole mile and use the header files instead of the implementation files to communicate your strategy to the central dispatcher: 由于您的Strategy.c显然已经通过名称(“#include”XYstrategy.h“)知道策略实例,您可以全力以赴,使用头文件而不是实现文件将您的策略​​传达给中央调度员:

MyFirstStrategy.h MyFirstStrategy.h

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

MyOtherStrategy.h MyOtherStrategy.h

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

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

This should work on any C compiler and platform without any link-time tricks. 这应该适用于任何C编译器和平台,没有任何链接时间技巧。

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

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