本篇文章基于micropython1.18版本分析,1.19版本及之后可能略有差异。

关于c模组的写法官方文档有介绍https://docs.micropython.org/en/latest/develop/cmodules.html,也可以参考micropython工程源码中其他的例子,这里不多介绍。

注册c模组

按照c模组构造方法写完c程序后,还需要再头文件中声明模组注册,注册模组有两种方式一种手动添加到头文件,另一个就是采用自动注册方式,以unix端口为例编译为例子说明:

1.手动注册

找到ports/unix/mpconfigport.h文件,很多外部c模组就在这里面声明注册的。以machine模组为例,需要添加以下语句注册声明

//声明模组对象结构体
extern const struct _mp_obj_module_t mp_module_machine;

//将模组名-模组指针 加入MICROPY_PORT_BUILTIN_MODULES 宏中,该宏最终会加入全局模组表中。
#define MICROPY_PORT_BUILTIN_MODULES \
...
    { MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&mp_module_machine) }, \
...

通过追踪该宏MICROPY_PORT_BUILTIN_MODULES 可以发现,他被加入到了文件py/objmodule.cmp_builtin_module_table[]里,代码如下:

// Global module table and related functions

STATIC const mp_rom_map_elem_t mp_builtin_module_table[] = {
    { MP_ROM_QSTR(MP_QSTR___main__), MP_ROM_PTR(&mp_module___main__) },
    { MP_ROM_QSTR(MP_QSTR_builtins), MP_ROM_PTR(&mp_module_builtins) },
    { MP_ROM_QSTR(MP_QSTR_micropython), MP_ROM_PTR(&mp_module_micropython) },

   ...省略部分代码

    #if MICROPY_PY_BUILTINS_FLOAT
    #if MICROPY_PY_MATH
    { MP_ROM_QSTR(MP_QSTR_math), MP_ROM_PTR(&mp_module_math) },
    #endif
    #if MICROPY_PY_BUILTINS_COMPLEX && MICROPY_PY_CMATH
    { MP_ROM_QSTR(MP_QSTR_cmath), MP_ROM_PTR(&mp_module_cmath) },
    #endif
    #endif
    #if MICROPY_PY_SYS
    { MP_ROM_QSTR(MP_QSTR_usys), MP_ROM_PTR(&mp_module_sys) },
    #endif
    #if MICROPY_PY_GC && MICROPY_ENABLE_GC
    { MP_ROM_QSTR(MP_QSTR_gc), MP_ROM_PTR(&mp_module_gc) },
    #endif
    #if MICROPY_PY_THREAD
    { MP_ROM_QSTR(MP_QSTR__thread), MP_ROM_PTR(&mp_module_thread) },
    #endif

    // extmod modules

    #if MICROPY_PY_UASYNCIO
    { MP_ROM_QSTR(MP_QSTR__uasyncio), MP_ROM_PTR(&mp_module_uasyncio) },
    #endif
   ...省略部分代码

    // extra builtin modules as defined by a port
    MICROPY_PORT_BUILTIN_MODULES

    #ifdef MICROPY_REGISTERED_MODULES
    // builtin modules declared with MP_REGISTER_MODULE()
    MICROPY_REGISTERED_MODULES
    #endif
};

mp_builtin_module_table[]里就是micropython中注册的所有模组了,按宏选择打开。还发现末尾有一个MICROPY_REGISTERED_MODULES宏,这个就关系到下面要说的自动注册模组方式了。

注:该方式属于历史遗留,在1.19版本之后该部分代码已被移除,mp_builtin_module_table[] 中仅保留MICROPY_REGISTERED_MODULES宏,也就是后续不管内置模组还是外部模组添加都要采用下面的自动注册方式

2.自动注册模组

假如我们写好了一个与具体硬件无关的c模组想要发布分享,或者并不想更改micropython中源代码部分作为独立的工程,那么就可以使用这种方式。源码中也给出了一个例子–uarray模组,找到文件py/modarray.c

#include "py/builtin.h"

#if MICROPY_PY_ARRAY

STATIC const mp_rom_map_elem_t mp_module_array_globals_table[] = {
    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uarray) },
    { MP_ROM_QSTR(MP_QSTR_array), MP_ROM_PTR(&mp_type_array) },
};

STATIC MP_DEFINE_CONST_DICT(mp_module_array_globals, mp_module_array_globals_table);

const mp_obj_module_t mp_module_uarray = {
    .base = { &mp_type_module },
    .globals = (mp_obj_dict_t *)&mp_module_array_globals,
};

MP_REGISTER_MODULE(MP_QSTR_uarray, mp_module_uarray, MICROPY_PY_ARRAY);

#endif

末尾加了一句MP_REGISTER_MODULE(MP_QSTR_uarray, mp_module_uarray, MICROPY_PY_ARRAY);这个就是用来声明自动注册的宏,该宏用法如下:

// Declare a module as a builtin, processed by makemoduledefs.py
// param module_name: MP_QSTR_<module name>
// param obj_module: mp_obj_module_t instance
// prarm enabled_define: used as `#if (enabled_define) around entry`

#define MP_REGISTER_MODULE(module_name, obj_module, enabled_define)

3个参数为模组名的qstr,模组对象指针,是否使能,最后加了这句且enabled_define为1那么编译micropython的时候就会自动帮你注册上。

自动注册过程依赖一个脚本工具py/makemoduledefs.py,通过这个脚本会检索你的c源码中含有MP_REGISTER_MODULE的语句提取出信息,然后生成一个头文件ports/unix/build-standard/genhdr/moduledefs.h在编译路径下。

// Automatically generated by makemoduledefs.py.

#if (MICROPY_PY_ARRAY)
    extern const struct _mp_obj_module_t mp_module_uarray;
    #define MODULE_DEF_MP_QSTR_UARRAY { MP_ROM_QSTR(MP_QSTR_uarray), MP_ROM_PTR(&mp_module_uarray) },
#else
    #define MODULE_DEF_MP_QSTR_UARRAY
#endif


#define MICROPY_REGISTERED_MODULES \
    MODULE_DEF_MP_QSTR_UARRAY \
// MICROPY_REGISTERED_MODULES

像ulab(micropython的类numpy数组操作库)这种三方模组就是采用自动注册方式。

我们自己开发的时候也尽量采取这种方式,便于模组独立维护管理。