diff --git a/docs/develop/cmodules.rst b/docs/develop/cmodules.rst index 849d0e60a..2d08df828 100644 --- a/docs/develop/cmodules.rst +++ b/docs/develop/cmodules.rst @@ -49,8 +49,9 @@ A MicroPython user C module is a directory with the following files: expanded copy of ``$(USERMOD_DIR)`` to ``SRC_USERMOD``, eg ``SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c`` - If you have custom ``CFLAGS`` settings or include folders to define, these - should be added to ``CFLAGS_USERMOD``, or ``CXXFLAGS_USERMOD``. + If you have custom compiler options (like ``-I`` to add directories to search + for header files), these should be added to ``CFLAGS_USERMOD`` for C code + and to ``CXXFLAGS_USERMOD`` for C++ code. See below for full usage example. @@ -58,124 +59,113 @@ A MicroPython user C module is a directory with the following files: Basic example ------------- -This simple module named ``example`` provides a single function -``example.add_ints(a, b)`` which adds the two integer args together and returns -the result. +This simple module named ``cexample`` provides a single function +``cexample.add_ints(a, b)`` which adds the two integer args together and returns +the result. It can be found in the MicroPython source tree and has +a source file and a Makefile fragment with content as descibed above:: -Directory:: + micropython/ + └──examples/ + └──usercmodule/ + └──cexample/ + ├── examplemodule.c + └── micropython.mk - example/ - ├── example.c - └── micropython.mk - - -``example.c`` - -.. code-block:: c - - // Include required definitions first. - #include "py/obj.h" - #include "py/runtime.h" - #include "py/builtin.h" - - // This is the function which will be called from Python as example.add_ints(a, b). - STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { - // Extract the ints from the micropython input objects - int a = mp_obj_get_int(a_obj); - int b = mp_obj_get_int(b_obj); - - // Calculate the addition and convert to MicroPython object. - return mp_obj_new_int(a + b); - } - // Define a Python reference to the function above - STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); - - // Define all properties of the example module. - // Table entries are key/value pairs of the attribute name (a string) - // and the MicroPython object reference. - // All identifiers and strings are written as MP_QSTR_xxx and will be - // optimized to word-sized integers by the build system (interned strings). - STATIC const mp_rom_map_elem_t example_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) }, - { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, - }; - STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); - - // Define module object. - const mp_obj_module_t example_user_cmodule = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t*)&example_module_globals, - }; - - // Register the module to make it available in Python - MP_REGISTER_MODULE(MP_QSTR_example, example_user_cmodule, MODULE_EXAMPLE_ENABLED); - - -``micropython.mk`` - -.. code-block:: make - - EXAMPLE_MOD_DIR := $(USERMOD_DIR) - - # Add all C files to SRC_USERMOD. - SRC_USERMOD += $(EXAMPLE_MOD_DIR)/example.c - - # We can add our module folder to include paths if needed - # This is not actually needed in this example. - CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR) - -Finally you will need to define ``MODULE_EXAMPLE_ENABLED`` to 1. This -can be done by adding ``CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1`` to -the ``make`` command, or editing ``mpconfigport.h`` or -``mpconfigboard.h`` to add - -.. code-block:: c - - #define MODULE_EXAMPLE_ENABLED (1) - -Note that the exact method depends on the port as they have different -structures. If not done correctly it will compile but importing will -fail to find the module. +Refer to the comments in these 2 files for additional explanation. +Next to the ``cexample`` module there's also ``cppexample`` which +works in the same way but shows one way of mixing C and C++ code +in MicroPython. Compiling the cmodule into MicroPython -------------------------------------- To build such a module, compile MicroPython (see `getting started -`_) with an -extra ``make`` flag named ``USER_C_MODULES`` set to the directory containing -all modules you want included (not to the module itself). For example: +`_), +applying 2 modifications: + +- an extra ``make`` flag named ``USER_C_MODULES`` set to the directory + containing all modules you want included (not to the module itself). + For building the example modules which come with MicroPython, + set ``USER_C_MODULES`` to the ``examples/usercmodule`` directory. + For your own projects it's more convenient to keep custom code out of + the main source tree so a typical project directory structure will look + like this:: + + my_project/ + ├── modules/ + │ └──example1/ + │ ├──example1.c + │ └──micropython.mk + │ └──example2/ + │ ├──example2.c + │ └──micropython.mk + └── micropython/ + ├──ports/ + ... ├──stm32/ + ... -Directory:: + with ``USER_C_MODULES`` set to the ``my_project/modules`` directory. - my_project/ - ├── modules/ - │ └──example/ - │ ├──example.c - │ └──micropython.mk - └── micropython/ - ├──ports/ - ... ├──stm32/ - ... +- all modules found in this directory will be compiled, but only those + which are explicitly enabled will be availabe for importing. Enabling a + module is done by setting the preprocessor define from its module + registration to 1. For example if the source code defines the module with -Building for stm32 port: + .. code-block:: c + + MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED); + + + then ``MODULE_CEXAMPLE_ENABLED`` has to be set to 1 to make the module available. + This can be done by adding ``CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1`` to + the ``make`` command, or editing ``mpconfigport.h`` or ``mpconfigboard.h`` + to add + + .. code-block:: c + + #define MODULE_CEXAMPLE_ENABLED (1) + + + Note that the exact method depends on the port as they have different + structures. If not done correctly it will compile but importing will + fail to find the module. + +To sum up, here's how the ``cexample`` module from the ``examples/usercmodule`` +directory can be built for the unix port: + +.. code-block:: bash + + cd micropython/ports/unix + make USER_C_MODULES=../../examples/usercmodule CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 all + +The build output will show the modules found:: + + ... + Including User C Module from ../../examples/usercmodule/cexample + Including User C Module from ../../examples/usercmodule/cppexample + ... + + +Or for your own project with a directory structure as shown above, +including both modules and building the stm32 port for example: .. code-block:: bash cd my_project/micropython/ports/stm32 - make USER_C_MODULES=../../../modules CFLAGS_EXTRA=-DMODULE_EXAMPLE_ENABLED=1 all + make USER_C_MODULES=../../../modules \ + CFLAGS_EXTRA="-DMODULE_EXAMPLE1_ENABLED=1 -DMODULE_EXAMPLE2_ENABLED=1" all Module usage in MicroPython --------------------------- -Once built into your copy of MicroPython, the module implemented -in ``example.c`` above can now be accessed in Python just -like any other builtin module, eg +Once built into your copy of MicroPython, the module +can now be accessed in Python just like any other builtin module, e.g. .. code-block:: python - import example - print(example.add_ints(1, 3)) + import cexample + print(cexample.add_ints(1, 3)) # should display 4 diff --git a/examples/usercmodule/cexample/examplemodule.c b/examples/usercmodule/cexample/examplemodule.c new file mode 100644 index 000000000..f608823c9 --- /dev/null +++ b/examples/usercmodule/cexample/examplemodule.c @@ -0,0 +1,34 @@ +// Include MicroPython API. +#include "py/runtime.h" + +// This is the function which will be called from Python as cexample.add_ints(a, b). +STATIC mp_obj_t example_add_ints(mp_obj_t a_obj, mp_obj_t b_obj) { + // Extract the ints from the micropython input objects. + int a = mp_obj_get_int(a_obj); + int b = mp_obj_get_int(b_obj); + + // Calculate the addition and convert to MicroPython object. + return mp_obj_new_int(a + b); +} +// Define a Python reference to the function above. +STATIC MP_DEFINE_CONST_FUN_OBJ_2(example_add_ints_obj, example_add_ints); + +// Define all properties of the module. +// Table entries are key/value pairs of the attribute name (a string) +// and the MicroPython object reference. +// All identifiers and strings are written as MP_QSTR_xxx and will be +// optimized to word-sized integers by the build system (interned strings). +STATIC const mp_rom_map_elem_t example_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cexample) }, + { MP_ROM_QSTR(MP_QSTR_add_ints), MP_ROM_PTR(&example_add_ints_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table); + +// Define module object. +const mp_obj_module_t example_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&example_module_globals, +}; + +// Register the module to make it available in Python. +MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule, MODULE_CEXAMPLE_ENABLED); diff --git a/examples/usercmodule/cexample/micropython.mk b/examples/usercmodule/cexample/micropython.mk new file mode 100644 index 000000000..dbfe3c5cb --- /dev/null +++ b/examples/usercmodule/cexample/micropython.mk @@ -0,0 +1,9 @@ +EXAMPLE_MOD_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(EXAMPLE_MOD_DIR)/examplemodule.c + +# We can add our module folder to include paths if needed +# This is not actually needed in this example. +CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR) +CEXAMPLE_MOD_DIR := $(USERMOD_DIR) diff --git a/examples/usercmodule/cppexample/example.cpp b/examples/usercmodule/cppexample/example.cpp new file mode 100644 index 000000000..06809732a --- /dev/null +++ b/examples/usercmodule/cppexample/example.cpp @@ -0,0 +1,17 @@ +extern "C" { +#include + +// Here we implement the function using C++ code, but since it's +// declaration has to be compatible with C everything goes in extern "C" scope. +mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj) { + // Prove we have (at least) C++11 features. + const auto a = mp_obj_get_int(a_obj); + const auto b = mp_obj_get_int(b_obj); + const auto sum = [&]() { + return mp_obj_new_int(a + b); + } (); + // Prove we're being scanned for QSTRs. + mp_obj_t tup[] = {sum, MP_ROM_QSTR(MP_QSTR_hellocpp)}; + return mp_obj_new_tuple(2, tup); +} +} diff --git a/examples/usercmodule/cppexample/examplemodule.c b/examples/usercmodule/cppexample/examplemodule.c new file mode 100644 index 000000000..ceb588bef --- /dev/null +++ b/examples/usercmodule/cppexample/examplemodule.c @@ -0,0 +1,25 @@ +#include + +// Define a Python reference to the function we'll make available. +// See example.cpp for the definition. +STATIC MP_DEFINE_CONST_FUN_OBJ_2(cppfunc_obj, cppfunc); + +// Define all properties of the module. +// Table entries are key/value pairs of the attribute name (a string) +// and the MicroPython object reference. +// All identifiers and strings are written as MP_QSTR_xxx and will be +// optimized to word-sized integers by the build system (interned strings). +STATIC const mp_rom_map_elem_t cppexample_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cppexample) }, + { MP_ROM_QSTR(MP_QSTR_cppfunc), MP_ROM_PTR(&cppfunc_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(cppexample_module_globals, cppexample_module_globals_table); + +// Define module object. +const mp_obj_module_t cppexample_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&cppexample_module_globals, +}; + +// Register the module to make it available in Python. +MP_REGISTER_MODULE(MP_QSTR_cppexample, cppexample_user_cmodule, MODULE_CPPEXAMPLE_ENABLED); diff --git a/examples/usercmodule/cppexample/examplemodule.h b/examples/usercmodule/cppexample/examplemodule.h new file mode 100644 index 000000000..d89384a63 --- /dev/null +++ b/examples/usercmodule/cppexample/examplemodule.h @@ -0,0 +1,5 @@ +// Include MicroPython API. +#include "py/runtime.h" + +// Declare the function we'll make available in Python as cppexample.cppfunc(). +extern mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj); diff --git a/examples/usercmodule/cppexample/micropython.mk b/examples/usercmodule/cppexample/micropython.mk new file mode 100644 index 000000000..e10d965a0 --- /dev/null +++ b/examples/usercmodule/cppexample/micropython.mk @@ -0,0 +1,12 @@ +CPPEXAMPLE_MOD_DIR := $(USERMOD_DIR) + +# Add our source files to the respective variables. +SRC_USERMOD += $(CPPEXAMPLE_MOD_DIR)/examplemodule.c +SRC_USERMOD_CXX += $(CPPEXAMPLE_MOD_DIR)/example.cpp + +# Add our module directory to the include path. +CFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR) +CXXFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR) + +# We use C++ features so have to link against the standard library. +LDFLAGS_USERMOD += -lstdc++ diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index f11d0b0d2..55399831a 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -7,11 +7,13 @@ CFLAGS += \ -fprofile-arcs -ftest-coverage \ -Wformat -Wmissing-declarations -Wmissing-prototypes \ -Wold-style-definition -Wpointer-arith -Wshadow -Wuninitialized -Wunused-parameter \ - -DMICROPY_UNIX_COVERAGE + -DMICROPY_UNIX_COVERAGE \ + -DMODULE_CEXAMPLE_ENABLED=1 -DMODULE_CPPEXAMPLE_ENABLED=1 LDFLAGS += -fprofile-arcs -ftest-coverage FROZEN_MANIFEST ?= $(VARIANT_DIR)/manifest.py +USER_C_MODULES = $(TOP)/examples/usercmodule MICROPY_ROM_TEXT_COMPRESSION = 1 MICROPY_VFS_FAT = 1 diff --git a/tests/unix/extra_coverage.py b/tests/unix/extra_coverage.py index 1c028506e..b4808993a 100644 --- a/tests/unix/extra_coverage.py +++ b/tests/unix/extra_coverage.py @@ -49,6 +49,16 @@ print(buf.write(bytearray(16))) # function defined in C++ code print("cpp", extra_cpp_coverage()) +# test user C module +import cexample + +print(cexample.add_ints(3, 2)) + +# test user C module mixed with C++ code +import cppexample + +print(cppexample.cppfunc(1, 2)) + # test basic import of frozen scripts import frzstr1 diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 514ff9437..257224108 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -145,6 +145,8 @@ OSError None None cpp None +5 +(3, 'hellocpp') frzstr1 frzstr1.py frzmpy1