2

我的 AVR 微控制器固件中有一个简单的命令行界面,基于这样的结构:

typedef struct {
    const char *name;
    const char *usage;
    const char *help;
    void (*handler)(char **last);
} command_t;

extern command_t *cli_commands[];

我希望能够以尽可能 DRY 的方式在不同的模块中声明命令。到目前为止,我有它,所以我定义我的函数是这样的:

COMMAND_IMPL(replay, "replay <n>", "Replay frame n") {
    // 'last' argument is for strtok_t
}

然后我必须在我的一个.c文件中有这个:

COMMAND_DECL(list);
COMMAND_DECL(clear);
COMMAND_DECL(replay);

COMMAND_TABLE
    COMMAND(list)
    COMMAND(clear)
    COMMAND(replay)
END_COMMAND_TABLE

我的宏看起来像这样:

#define COMMAND_IMPL(name, usage, help) \
    static void cli_handle_##name##_command(char **);\
    command_t cli_##name##_command = {#name, usage, help, cli_handle_##name##_command};\
    static void cli_handle_##name##_command(char **last)
#define COMMAND_DECL(name) extern command_t cli_##name##_command;
#define COMMAND_TABLE command_t *cli_commands[] = {
#define COMMAND(name) ((command_t *) &cli_##name##_command),
#define END_COMMAND_TABLE NULL };

可以做得比这更好吗?该项目已经绑定到 avr-gcc,所以我不介意它是否是一个 GCC-only 解决方案。使用链接器功能的解决方案也可以(我正在编译为 ELF)。我在想也许我可以将命令条目放在不同的部分,并将其链接到已知的某个地方,但我想不出它会如何被终止。

4

1 回答 1

1

您可以section像这样使用该属性(这是 linux 内核导出其功能的方式):

#define COMMAND_IMPL(name, usage, help) \
    static void cli_handle_##name##_command(char **); \
    command_t cli_##name##_command __attribute((section("commands"))) = { #name, usage, help, cli_handle_##name##_command }; \
static void cli_handle_##name##_command(char **last)

然后,您可以使用ld脚本(请参阅http://www.math.utah.edu/docs/info/ld_3.html)获取“命令”部分的开始/结束地址:

...
SECTION commands ALIGN(4) : {
    commands_begin = .;
    *(commands)
    commands_end = .;
}

最后,声明两个变量:

extern command_t commands_begin, commands_end;

然后,您可以使用这些变量的地址来获取命令结构的整个列表,即

for (command_t *cmd = &commands_begin; cmd != &commands_end; cmd++)
    ...

注意&commands_end在最后一个命令之外的点。

于 2013-06-26T21:47:42.540 回答