From 18c9e306c2e09a4a47eb8c84897ac766cdd79453 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 7 Jun 2019 00:19:48 +0800 Subject: [PATCH] examples/system: add C++ RTTI example --- examples/system/cpp_rtti/CMakeLists.txt | 6 ++ examples/system/cpp_rtti/Makefile | 9 ++ examples/system/cpp_rtti/README.md | 69 ++++++++++++ examples/system/cpp_rtti/example_test.py | 37 +++++++ examples/system/cpp_rtti/main/CMakeLists.txt | 2 + examples/system/cpp_rtti/main/component.mk | 3 + .../cpp_rtti/main/rtti_example_main.cpp | 100 ++++++++++++++++++ examples/system/cpp_rtti/sdkconfig.defaults | 1 + 8 files changed, 227 insertions(+) create mode 100644 examples/system/cpp_rtti/CMakeLists.txt create mode 100644 examples/system/cpp_rtti/Makefile create mode 100644 examples/system/cpp_rtti/README.md create mode 100644 examples/system/cpp_rtti/example_test.py create mode 100644 examples/system/cpp_rtti/main/CMakeLists.txt create mode 100644 examples/system/cpp_rtti/main/component.mk create mode 100644 examples/system/cpp_rtti/main/rtti_example_main.cpp create mode 100644 examples/system/cpp_rtti/sdkconfig.defaults diff --git a/examples/system/cpp_rtti/CMakeLists.txt b/examples/system/cpp_rtti/CMakeLists.txt new file mode 100644 index 0000000000..2e8f97000e --- /dev/null +++ b/examples/system/cpp_rtti/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(cpp_rtti) diff --git a/examples/system/cpp_rtti/Makefile b/examples/system/cpp_rtti/Makefile new file mode 100644 index 0000000000..72303c2e67 --- /dev/null +++ b/examples/system/cpp_rtti/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := cpp_rtti + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/system/cpp_rtti/README.md b/examples/system/cpp_rtti/README.md new file mode 100644 index 0000000000..8ab0c985aa --- /dev/null +++ b/examples/system/cpp_rtti/README.md @@ -0,0 +1,69 @@ +# Example: C++ run-time type info (RTTI) + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates usage of the RTTI feature in ESP-IDF. + +By default, RTTI support is disabled in ESP-IDF. It can be enabled using `CONFIG_COMPILER_CXX_RTTI` configuration option. + +In this example, `sdkconfig.defaults` file sets `CONFIG_COMPILER_CXX_RTTI` option. This enables compile time support for RTTI (`-frtti` compiler flag). + +The example prints demangled type names of a few objects and functions, obtained from `typeinfo().name`. The example also generates several objects of two classes, derived from a common base class. For each object, it is demonstrated that `dynamic_cast` behaves as expected, returning non-NULL when casting to the real object type, and NULL when casting to a different type. + +## How to use example + +### Configure the project + +To run this example, no additional configuration is necessary. + +### Build and Flash + +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. + +## Example Output + +``` +Type names of a few objects: + Type name of std::cout is: std::ostream + Type name of std::cin is: std::istream + Type of app_main is: void () + Type name of a lambda function is: app_main::{lambda(int, int)#1} + +Generating 5 random objects and printing their types: +obj->name() is: DerivedB + typeid(*obj).name() is: DerivedB + dynamic_cast(obj)=0 + dynamic_cast(obj)=0x3ffb7e88 + +obj->name() is: DerivedB + typeid(*obj).name() is: DerivedB + dynamic_cast(obj)=0 + dynamic_cast(obj)=0x3ffb7e9c + +obj->name() is: DerivedA + typeid(*obj).name() is: DerivedA + dynamic_cast(obj)=0x3ffb7eb0 + dynamic_cast(obj)=0 + +obj->name() is: DerivedB + typeid(*obj).name() is: DerivedB + dynamic_cast(obj)=0 + dynamic_cast(obj)=0x3ffb7ec4 + +obj->name() is: DerivedA + typeid(*obj).name() is: DerivedA + dynamic_cast(obj)=0x3ffb7ed8 + dynamic_cast(obj)=0 + +Example finished. +``` diff --git a/examples/system/cpp_rtti/example_test.py b/examples/system/cpp_rtti/example_test.py new file mode 100644 index 0000000000..8d0d9d7f79 --- /dev/null +++ b/examples/system/cpp_rtti/example_test.py @@ -0,0 +1,37 @@ +from __future__ import print_function +import os +import sys + +try: + import IDF +except ImportError: + # this is a test case write with tiny-test-fw. + # to run test cases outside tiny-test-fw, + # we need to set environment variable `TEST_FW_PATH`, + # then get and insert `TEST_FW_PATH` to sys path before import FW module + test_fw_path = os.getenv('TEST_FW_PATH') + if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + import IDF + + +@IDF.idf_example_test(env_tag='Example_WIFI') +def test_real_time_stats_example(env, extra_data): + dut = env.get_dut('cpp_rtti', 'examples/system/cpp_rtti') + dut.start_app() + + dut.expect('Type name of std::cout is: std::ostream') + dut.expect('Type name of std::cin is: std::istream') + dut.expect('Type of app_main is: void ()') + dut.expect('Type name of a lambda function is: app_main::{lambda(int, int)#1}') + + dut.expect('dynamic_cast(obj)=0') + dut.expect('dynamic_cast(obj)=0x') + dut.expect('dynamic_cast(obj)=0') + dut.expect('dynamic_cast(obj)=0x') + + dut.expect('Example finished.') + + +if __name__ == '__main__': + test_real_time_stats_example() diff --git a/examples/system/cpp_rtti/main/CMakeLists.txt b/examples/system/cpp_rtti/main/CMakeLists.txt new file mode 100644 index 0000000000..ee808f5080 --- /dev/null +++ b/examples/system/cpp_rtti/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "rtti_example_main.cpp" + INCLUDE_DIRS ".") diff --git a/examples/system/cpp_rtti/main/component.mk b/examples/system/cpp_rtti/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/system/cpp_rtti/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/system/cpp_rtti/main/rtti_example_main.cpp b/examples/system/cpp_rtti/main/rtti_example_main.cpp new file mode 100644 index 0000000000..b6417547ed --- /dev/null +++ b/examples/system/cpp_rtti/main/rtti_example_main.cpp @@ -0,0 +1,100 @@ +/* C++ run-time type info (RTTI) example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include + +using std::cout; +using std::endl; +using std::string; +using std::shared_ptr; +using std::make_shared; + +class Base; +class DerivedA; +class DerivedB; + +static string demangle(const char* name); + +class Base +{ +public: + virtual ~Base() {} ; + virtual string name() = 0; + static shared_ptr make_random_derived(); +}; + +class DerivedA : public Base +{ +public: + string name() override { return "DerivedA"; } +}; + +class DerivedB : public Base +{ +public: + string name() override { return "DerivedB"; } +}; + +/* Creates either DerivedA or DerivedB, depending on a random number */ +shared_ptr Base::make_random_derived() +{ + if (std::rand() % 2 == 0) { + return make_shared(); + } else { + return make_shared(); + } +} + +/* Inside a .cpp file, app_main function must be declared with C linkage */ +extern "C" void app_main() +{ + /* Demonstrate typeid().name() */ + cout << "Type names of a few objects:" << endl << '\t'; + cout << "Type name of std::cout is: " << demangle(typeid(std::cout).name()) << endl << '\t'; + cout << "Type name of std::cin is: " << demangle(typeid(std::cin).name()) << endl << '\t'; + cout << "Type of app_main is: " << demangle(typeid(app_main).name()) << endl << '\t'; + auto sum = [](int x, int y) -> int { return x + y; }; + cout << "Type name of a lambda function is: " << demangle(typeid(sum).name()) << endl << endl; + + /* Demonstrate dynamic_cast */ + std::vector> objects(5); + cout << "Generating " << objects.size() << " random objects and printing their types:" << endl; + std::generate(objects.begin(), objects.end(), Base::make_random_derived); + for (auto &obj: objects) { + cout << "obj->name() is: " << obj->name() << endl << '\t'; + cout << "typeid(*obj).name() is: " << demangle(typeid(*obj).name()) << endl << '\t'; + + const DerivedA* cast_to_derived_a = dynamic_cast(obj.get()); + const DerivedB* cast_to_derived_b = dynamic_cast(obj.get()); + + cout << "dynamic_cast(obj)=" << static_cast(cast_to_derived_a) << endl << '\t'; + cout << "dynamic_cast(obj)=" << static_cast(cast_to_derived_b) << endl << endl; + } + + cout << "Example finished." << endl; +} + +/* Helper function which converts typeid().name() to a human-readable type name */ +static std::string demangle(const char* name) +{ + int status = 0; + char* result = abi::__cxa_demangle(name, NULL, NULL, &status); + string str_result; + if (status == 0) { + str_result = result; + } else { + str_result = name; + } + free(result); + return str_result; +} diff --git a/examples/system/cpp_rtti/sdkconfig.defaults b/examples/system/cpp_rtti/sdkconfig.defaults new file mode 100644 index 0000000000..3d46789938 --- /dev/null +++ b/examples/system/cpp_rtti/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_COMPILER_CXX_RTTI=y