| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|---|
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates features of ESP-IDF build system related to link time registration of plugins. Link time registration of plugins is often used to add multiple implementations of a certain feature without having to make the application aware of all these implementations. With this approach, adding a new implementation is often as simple as adding a new source file or a new component. Aside from plugins, link time registration may be used for other purposes, such as automatic registration of test cases.
When using link time registration, there are typically two challenges: getting the plugin code linked into the application and enumerating the plugins at run time. The following sections explain these problems and the solutions to them.
When GNU linker (ld) links a static library, it considers each object file in the library separately. The object file is ignored if it doesn't resolve any unresolved references known to the linker at that moment. With link-time plugin registration this is typically the case — the application doesn't explicitly reference any plugins, so the linker sees no reason to include the respective object files into the executable.
Aside from adding an explicit reference from the application to the plugin object file, there are two common ways to resolve this issue:
-Wl,--whole-archive and -Wl,--no-whole-archive flags.ESP-IDF build system implements the 2nd approach by providing a WHOLE_ARCHIVE component property. It can be set in component CMakeLists.txt in two ways. One is to add WHOLE_ARCHIVE option when calling idf_component_register:
idf_component_register(SRCS file.c
WHOLE_ARCHIVE)
Another is to call idf_component_set_property after registering the component:
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE TRUE)
This will instruct the build system to surround the component library with whole-archive flags on the linker command line, ensuring that all object files from the library get included into the final application.
Note that the linker also performs "garbage collection" at the end of the linking process, eliminating all functions and global variables which are not referenced anywhere. This is addressed in the current example using __attribute__((constructor)) function attribute (for dynamic registration) and KEEP() linker fragment flag (for static registration).
To make use of the plugins linked into the application, the application must somehow enumerate them. There are 2 common ways to register and enumerate the plugins: dynamic and static. This example demonstrates both approaches.
With this approach, each plugin module has a function with __attribute__((constructor)) attribute (in C) or a static global object with a non-trivial constructor (in C++). Startup code calls all constructor functions before the application entry point (app_main) is executed. Plugin constructor functions then register themselves by calling a function defined in the application. As an example, this registration function can, add structures describing the plugins into a linked list.
This approach relies on plugin description structures being collected into an array at link time.
For each plugin, a structure describing the plugin (or a pointer to it) is placed into some special input section using __attribute((section(".plugins_desc"))). Using the linker script generator features in ESP-IDF, all entries from this input section can be gathered into a continuous array, surrounded by some symbols (e.g. _plugins_array_start, _plugins_array_end). At run time, the application casts the &_plugins_array_start pointer to the plugin description structure pointer and then iterates over structures collected from all plugins.
This example contains 4 components:
main — Only calls two sample functions defined in plugins component.plugins — The main part of the plugin system.For dynamic registration, it provides an API which plugin components call to register themselves (example_plugin_register).
It also provides two sample functions for the application:
example_plugins_list: prints the list of registered plugins. This function demonstrates static registration.example_plugins_greet: calls a function defined by each plugin with a given argument. This function demonstrates working with dynamically registered plugins.plugin_hello and plugin_nihao — two almost identical components, each provides one plugin.Note that multiple plugins may also be defined in the same component.
Please refer to the comments in the example code for a more detailed description.
This example runs on any ESP development board, no special hardware is required.
Build the project and flash it to the board, then run monitor tool to view serial output:
idf.py -p PORT flash monitor
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type Ctrl-].)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
Nihao plugin performing self-registration...
Successfully registered plugin 'Nihao'
Hello plugin performing self-registration...
Successfully registered plugin 'Hello'
I (325) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
List of plugins:
- Plugin 'Hello', function greet=0x400d4f40
0x400d4f40: plugin_hello_greet at /home/user/esp/esp-idf/examples/build_system/cmake/plugins/build/../components/plugin_hello/plugin_hello.c:14
- Plugin 'Nihao', function greet=0x400d4f70
0x400d4f70: plugin_nihao_greet at /home/user/esp/esp-idf/examples/build_system/cmake/plugins/build/../components/plugin_nihao/plugin_nihao.c:14
Calling greet function of plugin 'Hello'...
Hello, World!
Done with greet function of plugin 'Hello'.
Calling greet function of plugin 'Nihao'...
你好 World!
Done with greet function of plugin 'Nihao'.
When implementing the approaches described in this example, the following issues may occur:
build directory of the project. Look for the object file where the constructor function is defined. If the object file and the constructor function are missing, it means that the object file was discarded. Double-check that the WHOLE_ARCHIVE property of the component is set correctly. Verify that on the linker command line, the component library is surrounded by -Wl,--whole-archive, -Wl,--no-whole-archive. To see the linker command line, build the project with verbose (-v) flag.WHOLE_ARCHIVE property of the component is set correctly (see the instructions above).__attribute((section(...))) use the same section name. Check that the linker fragment rule uses KEEP() flag.build/esp-idf/esp_system/ld/sections.ld). Check that the rules for placing the plugin description structure have correct precedence with respect to other rules in the linker script.