C、【远程过程调用】远程定义和调用函数

问题描述

我正在编写一个固件,我想在其中定义多个 .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,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 编写的固件中复制这一点,是否有一种简单且类似的方法来动态声明函数列表?

解决方法

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

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

这相对非常非常简单。您将按照您希望的方式格式化的数据放入一个部分。 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"))),您可以制作一个包含所有信息的大型结构数组。

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

请注意,这是一些约定,即带有前导点的部分是相当保留的。对于自定义部分,最好使用没有前导点的部分。

怎么可能?

编译器和链接器公开了一个特定的接口来处理用户定义的部分,允许这样做。

,

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

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

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

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