在ESP-IDF中使用C和C++进行混合编译

ESP-IDF是Espressif Systems开发的官方IoT开发框架,用于编程和开发ESP32系列的微控制器。虽然ESP-IDF主要使用C语言编写,但它也支持使用C++进行开发

为什么要进行混合编译?

C++是一种功能强大的编程语言,它提供了许多C语言不具备的特性,如类(对象导向编程)、异常处理、函数重载等。然而,C语言在嵌入式系统开发中仍然占据主导地位,因为它更接近硬件,运行效率高,且占用资源少。

因此,混合编译允许开发者在同一个项目中利用C++的高级特性和C的效率。例如,你可以在C++中编写面向对象的代码,用于处理复杂的逻辑和数据结构,同时在C中编写底层的硬件操作代码。

如何进行混合编译?

在ESP-IDF中进行混合编译主要涉及到两个方面:源文件的组织链接性的处理

源文件的组织

在ESP-IDF项目中,源文件通常按照组件(component)来组织。每个组件都有自己的目录,包含了该组件的源文件和头文件,以及一个CMakeLists.txt文件,用于告诉CMake如何编译这些源文件。

在一个混合编译的项目中,你可以有一些组件是用C编写的,一些组件是用C++编写的。例如,你可以有一个用C编写的LED组件,用于控制LED的亮度和颜色,同时有一个用C++编写的KEY组件,用于初始化和扫描按键键值。

用一个按键控制LED的项目举例,下面是这个项目的文件构成

在这里插入图片描述
main.cppLED.cLED.hKEY.cppKEY.h是我们项目中需要编译链接的文件,它们和CMakeLists.txt的文件结构如下

  • 02KEY
    • components
      • LED
        • LED.c
        • LED.h
      • KEY
        • KEY.cpp
        • KEY.h
    • CMakeLists.txt
  • main
    • main.cpp
    • CMakeLists.txt

components组件下的.cpp/,c和.h

components文件下的CMakeLists.txt指定了该组件的源文件、头文件目录和依赖项。将 LED/LED.cKEY/KEY.cpp文件作为源文件,LEDKEY目录作为头文件目录,并将 driver 组件作为依赖项(driver组件是在项目中用到的,所以要将它加入到依赖项)。

idf_component_register(
    SRCS "LED/LED.c" "KEY/KEY.cpp"
    INCLUDE_DIRS "LED" "KEY"
    REQUIRES driver
)

main文件下的main.c

第一步
将main文件夹下的main.c重命名为main.cpp

第二步
main文件下的CMakeLists.txt同样只需要将main.c改为main.cpp即可

项目创建自动生成的:

idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS ".")

修改之后的

idf_component_register(SRCS "main.cpp"
                    INCLUDE_DIRS ".")

链接性的处理

当C和C++代码在同一个项目中混合使用时,一个重要的问题是链接性(linkage)。链接性决定了一个符号(如函数或变量)在链接时如何被处理。C++支持函数重载,因此在编译后,C++函数的名字会被修饰(mangled)以表示它们的参数类型。然而,C语言没有这个特性,因此C函数的名字在编译后保持不变。

为了解决这个问题,C++提供了extern "C"这个关键字,用于声明一个符号使用C链接。当C++编译器看到extern "C"时,它会知道后面的代码应该按照C的规则来处理,因此不会对函数名进行修饰。

在ESP-IDF中,如果你有一个C++组件需要调用C组件的函数,你可以在C++代码中这样声明C函数:

extern "C" void led_set(int date);

同样,如果你有一个C组件需要调用C++组件的函数,需要在C代码中声明C++函数:

extern void led_read(void);

注意,C代码中不需要使用extern "C",因为C编译器不支持这个关键字。

下面我们继续拿刚刚按键控制LED的项目举例

以下是main.cppLED.cLED.hKEY.cppKEY.h文件的处理方式:

  • main.cpp:在这个文件中,我们了包含LED.hKEY.h。而void app_main(void)是ESP-IDF为我们提供的主程序,我们需要使用extern "C"来声明这个主程序

    #include "LED.h"
    #include "KEY.h"
    
    extern "C" void app_main(void){
    //程序实现-----
    }
    
  • LED.c和LED.h:这两个文件应该是C语言编写的,所以我们不需要做任何特殊的处理。

  • KEY.cpp和KEY.h:这两个文件是C++编写的。在KEY.h中,我们使用extern "C"来包含类定义的所有内容。在KEY.cpp中,需要使用extern "C"来定义这些成员函数

    KEY.h

    // KEY.h
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    class Key {
    public// 构造函数
    	Key(gpio_num_t pin);
    }
    
    #ifdef __cplusplus
    }
    #endif
    

    KEY.cpp

    // KEY.cpp
        #include "KEY.h"
    
        extern "C" Key::Key(gpio_num_t pin): pin(pin) {
            // ...
        }
    

注意 注意 类的成员函数不要使用inline(内联)要不然.h和.cpp文件可能会无法链接到,我就踩了这个坑😭😭一直以为是CMake写的不对,所以链接不上捣鼓了一天,最后都准备要把ESP-IDF给卸载重装了,在卸载前我还是不信邪,我把程序发给ChatGPT问它有什么问题,好家伙ChatGPT直接点出了问题所在,类的成员函数在类外定义时我用了inline导致的.h和.cpp链接不到,好在解决了,怎么没早点想到让ChatGPT排查错误呢😂😂,学到了,学到了,大家遇到什么问题记得问问AI真的有奇效。

结语

ESP-IDF同样提供了关于C++使用的例程,可以在你电脑中的ESP-IDF安装路径下找到

D:\Espressif\frameworks\esp-idf-v5.2.1\examples\cxx

关于ESP-IDF C++的更多内容可以阅读下下方的官方文档
ESP-IDF C++支持

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐