0

我正在编写一个固件,我想在多个 .c 文件中定义函数“命令”,并自动将它们插入函数指针列表中。随后,我想从串行端口调用数组中存在的这些命令之一,并使用参数列表执行它。

有一个用于 3d 打印机的固件,我依靠它来实现这样的事情。 https://github.com/KevinOConnor/klipper
使用的软件结构是这个“远程过程调用” https://en.wikipedia.org/wiki/Remote_procedure_call
在这个固件中,命令在.ch文件中实现并且有.h 中没有原型
例如:

void command_set_digital_out(uint32_t *args)
{
    gpio_out_setup(args[0], args[1]);
}
DECL_COMMAND(command_set_digital_out, "set_digital_out pin=%u value=%c");

以下是使用的定义列表

// Declare a function to run when the specified command is received
#define DECL_COMMAND_FLAGS(FUNC, FLAGS, MSG)                    \
    DECL_CTR("DECL_COMMAND_FLAGS " __stringify(FUNC) " "        \
             __stringify(FLAGS) " " MSG)
#define DECL_COMMAND(FUNC, MSG)                 \
    DECL_COMMAND_FLAGS(FUNC, 0, MSG)

// Declare a compile time request
#define DECL_CTR(REQUEST)                                               \
    static char __PASTE(_DECLS_, __LINE__)[] __attribute__((used))      \
        __section(".compile_time_request") = (REQUEST)

#define __stringify_1(x)        #x
#define __stringify(x)          __stringify_1(x)

#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)

随后,当固件获取串行代码时,它能够根据请求调用这些声明的函数。这怎么可能 ?
我无法复制这一点,在我为 platformio 中的 AVR 编写的固件中,是否有一种简单且类似的方法来动态声明函数列表?

4

2 回答 2

1

我应该说我根本不喜欢“在数组中自动声明函数”的想法。将数据放在部分中是巧妙的技巧,但它只是意大利面条代码 - 代码的不同部分通过不可见的链接连接 - 这可能会导致无法维护的混乱。我建议不要求助于编译器技巧,而是在一个地方编写一个具有清晰、简单、可读和可维护的代码的大数组。

是否有一种简单且类似的方法来动态声明函数列表?

这相对非常非常简单。您可以将按您希望的方式格式化的数据放入一个部分中。Gcc 编译器从所有文件中提取所有部分并将它们连接起来(假设以随机顺序)并生成符号 -__start_SECTIONAME__end_SECTIONAME可以使用它来迭代部分中的元素。

以下代码:

// autofuncs.h ----------------------------------------------------------------------
// a header for the funcionality
#include <errno.h>
#include <string.h>
#include <stddef.h>
#include <assert.h>
#include <stdio.h>

// we'll only need name and a function pointer
struct autofunc_s {
    const char *name;
    int (*main)(int argc, char **argv);
};

// add struct autofunc_s element in section named autofunc
#define AUTOFUNC_ADD(name, func) \
__attribute__((__section__("autofunc"))) \
const struct autofunc_s _autofunc_##func = { name, func };

// a simple runner
// find a function named argv[0] and execute it
static int autofunc_exec(int argc, char **argv) {
    extern const struct autofunc_s __start_autofunc;
    extern const struct autofunc_s __stop_autofunc;
    const struct autofunc_s *it = &__start_autofunc;
    const size_t count = &__stop_autofunc - &__start_autofunc;
    if (argc) {
        for (size_t i = 0; i < count; ++i) {
            if (strcmp(it[i].name, argv[0]) == 0) {
                assert(it[i].main != NULL);
                return it[i].main(argc, argv);
            }
        }
    }
    printf("Command not found\n");
    return ENOENT;
}

// echo.c ----------------------------------------------------------------------
#include "autofuncs.h"
#include <stdio.h>

// example function to run
static int echo(int argc, char **argv) {
    for (int i = 1; i < argc; ++i) {
        printf("%s%s", argv[i], i + 1 == argc ? "\n" : " ");
    }
    return 0;
}
AUTOFUNC_ADD("echo", echo)

// main.c ----------------------------------------------------------------------
// a very very short main that shows the funcionality
#include "autofuncs.h"

int main(int argc, char **argv) {
    // You would possibly here read one line from serial device
    // and tokenize the line on whitespaces here in some loop
    argc--; argv++;
    return autofunc_exec(argc, argv);
}

然后编译并链接:

$ gcc main.c echo.c

你可以:

$ ./a.out something
Command not found
$ ./a.out echo 1 2 3
1 2 3

使用相同的方法而不是使用__attribute__((__section__("autofunc")))您可以制作一大堆结构,将所有信息放在一个地方。

许多编译器有不同的语法来说明如何将数据放在一个节中以及如何到达该节。研究你的编译器文档。

请注意,这是一些约定,带有前导点的部分是相当保留的。对于自定义部分,更喜欢使用不带前导点的部分。

这怎么可能 ?

编译器和链接器公开了一个特定的接口来与用户定义的部分一起工作,从而允许这样做。

于 2021-05-03T12:53:02.783 回答
0

我试图寻找一个已经存在的解决方案来实现这个软件结构“远程过程调用”,我找到了这个用于 arduino 的库:https ://simplerpc.readthedocs.io/en/latest/

在这个库中,我可以定义通信接口和远程功能。
我需要从 C# 命令这个固件!

问题是这个库是用 .tcc 编写和编译的,这是我第一次看到这个扩展。

如果我无法在 tcc 中使用这个库,有人知道类似的库吗?

于 2021-05-04T12:02:07.157 回答