0

我正在做一个需要为几个 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);

我的想法是有一个条件语句来随意包含特定行为,以便根据包含的实体,编译器将对上述函数的调用链接到特定包含库中的函数。因此,在开始时,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){
     ...
   }
}

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

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

响应 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()
4

1 回答 1

0

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

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

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

如果链接器命令行会显式列出目标文件entity_1.o,并且entity_2.o我会收到有关重复符号的错误消息init_entity

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

  1. 确保仅使用选定的实体来构建程序。

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


对于第一种方法,您可以在CMakeLists.txt. 有关示例,请参阅https://stackoverflow.com/a/15212881/10622916 。也许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();
}

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

于 2020-11-17T12:33:40.200 回答