esp-idf:有条件地包含具有相同功能的组件

esp-idf: Conditional inclusion of components with same functions

我正在做一个项目,需要为几个 esp32 开发固件。所有微控制器共享一个通用代码来处理 wifi 和 mqtt,但是它们都有不同的行为,这些行为在特定组件中定义。我的项目结构是这样的:

    - CMakeLists.txt
    - Makefile
    - sdkconfig
    - main
        - CMakeLists.txt
        - main.c
    - components
        - wifi_fsm
            - wifi_fsm.h
            - wifi_fsm.c
            - CMakeLists.txt
        - mqtt_fsm
            - mqtt_fsm.h
            - mqtt_fsm.c
            - CMakeLists.txt
        - entity_1
            - entity_1.h
            - entity_1.c
            - CMakeLists.txt
        - entity2
            - entity2.h
            - entity2.c
            - CMakeLists.txt
        ...

每个实体都定义了一些具有标准名称的函数,这些函数为实体本身实现了特定的逻辑,并在共享代码(main、wifi_fsm、mqtt_fsm)中被调用。

void init_entity();                  // called in main.c
void http_get(char *buf);            // called in wifi_fsm
void http_put(char *buf);
void mqtt_msg_read(char *buf);       // called in mqtt_fsm
void mqtt_msg_write(char *buf);

我的想法是有一个条件语句来随意包含一个特定的行为,这样根据包含的实体,编译器将 link 对上面的函数的调用到那些在特定包含的实体中找到的函数图书馆。因此,在 main.c 的开头,我只是添加了以下几行,目的是必须更改唯一定义的预处理器符号以针对不同的实体行为进行编译。

#define ENTITY_1

#ifdef ENTITY_1
  #include "entity_1.h"
#elif defined ENTITY_2
  #include "entity_2.h"
#elif ...
#endif

#include "wifi_fsm.h"
#include "mqtt_fsm.h"

void app_main(void)
{
   while(1){
     ...
   }
}

一方面,编译器显然工作正常,编译成功,没有错误或警告,这意味着包含链工作正确,否则将抛出标准函数的重名错误。另一方面,它总是 link 按字母顺序针对第一个实体执行,例如执行组件 entity_1 的 init_entity() 中包含的代码。如果我重命名 entity_1 中的标准函数,那么它 link 反对 entity_2。

如果上述方法错误,我可能会使用指向标准调用的指针 linked 到每个实体中的特定函数,但我想首先了解我的方法有什么问题。

EDIT 响应 Bodo 的请求(CMakeFile.txt 的内容)

项目:

cmake_minimum_required(VERSION 3.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(proj)

主要:

set(COMPONENT_REQUIRES )
set(COMPONENT_PRIV_REQUIRES )

set(COMPONENT_SRCS "main.c")
set(COMPONENT_ADD_INCLUDEDIRS "")

register_component()

组件:

set(COMPONENT_SRCDIRS "src")
set(COMPONENT_ADD_INCLUDEDIRS "include")

set(COMPONENT_REQUIRES log freertos driver nvs_flash esp_http_server mqtt)

register_component()

这个答案是基于猜测的,因为我没有足够的信息。出于同样的原因,它在某些部分是不完整的,或者可能不完全符合问题的用例。

有关如何构建项目的详细信息似乎隐藏在 cmake 包含文件(如 project.cmake 或嵌套包含文件中)。

我的猜测是构建系统从每个单独组件的源代码创建库,然后将主对象文件与库链接。在这种情况下,链接器会在第一个满足依赖关系的库中找到像 init_entity 这样的符号。这意味着将使用链接器命令行中首先列出的库(=组件)。

如果链接器命令行明确列出目标文件 entity_1.oentity_2.o,我会收到有关重复符号 init_entity.

的错误消息

我可以提出两种解决问题的方法:

  1. 确保只有选定的实体用于构建程序。

  2. 使标识符名称在所有实体中唯一,并使用预处理器宏根据所选实体选择正确的名称。


对于第一种方法,您可以在 CMakeLists.txt 中使用条件语句。有关示例,请参见 。也许 register_component() 负责将组件添加到构建中。在这种情况下,您可以将其包装在一个条件中。

但是如果文件是自动生成的,修改 CMakeLists.txt 可能是错误的。


对于第二种方法,您应该重命名实体中的标识符以使其唯一。相应的头文件可以定义一个宏,将用于标识符的通用名称替换为所选实体的特定标识符。

在使用所选实体的代码中,您将始终使用通用名称,而不是单独的名称。

示例:

entity_1.c:

#include "entity_1.h"

void init_entity_1(void)
{
}

entity_2.c:

#include "entity_2.h"

void init_entity_2(void)
{
}

entity_1.h:

void init_entity_1(void);
// This replaces the token/identifier "init_entity" with "init_entity_1" in subsequent source lines
#define init_entity init_entity_1
// or depending on the parameter list something like
// #define init_entity() init_entity_1()
// #define init_entity(x,y,z) init_entity_1(y,x,z)

entity_2.h:

void init_entity_2(void);
#define init_entity init_entity_2

main.c

#define ENTITY_1

#ifdef ENTITY_1
  #include "entity_1.h"
#elif defined ENTITY_2
  #include "entity_2.h"
#elif ...
#endif


void some_function(void)
{
    init_entity();
}

在本例中 #define ENTITY_1,预处理器会将 some_function 更改为

void some_function(void)
{
    init_entity_1();
}

在编译步骤之前,链接器将使用 entity_1.c 中的 init_entity_1。优化链接器可能会忽略目标文件 entity_2.o 或相应的库,因为它未被使用。