阅读micropyton源码-添加C扩展类模块(2)
阅读micropyton源码-添加C扩展类模块(2)
苏勇,2021年8月
文章目录
- 阅读micropyton源码-添加C扩展类模块(2)
- 看看machine_pin_type实例的定义
- 特别说明
看看machine_pin_type实例的定义
回到ports/mimxrt/machine_pin.c文件,同machine_pin_type并列定义的还有“machine_pin_af_type”,在“pin.h”和“ports/mimxrt/boards/mimxrt_prefix.c”文件中有所涉及,关于指定引脚功能复用的,似乎也作为一个实例对象定义的,但未被注册到任何模块中,暂且放过,待看完machine_pin_type后再回来看。
至于“machine_pin_irq_methods”,仍是在定义machine_pin_type内所属的方法中被调用,等下会看到。
交待了旁枝末节,终于可以静下心来关注machine_pin_type了。
const mp_obj_type_t machine_pin_type = {{&mp_type_type},.name = MP_QSTR_Pin,.print = machine_pin_obj_print,.call = machine_pin_obj_call,.make_new = mp_pin_make_new,.locals_dict = (mp_obj_dict_t *)&machine_pin_locals_dict,
};
整个machine_pin.c就是为了填充这一个mp_obj_type_t类型的结构体实例,此处用到的是“硬回调”的编码模式。 关于“mp_obj_type_t”的定义,具体可参见“py/obj.h”文件,此处仅对必要的字段进行解释。
“&mp_type_type”指定本模块的基类是一个类型对象,直接继承了一些类型(type)对象的方法。“mp_type_type”在dynruntime.h文件中定义,同mp_type_type并列定义的还有mp_type_str、mp_type_tuple、mp_type_list等。
“.name”中登记的是本模块的名字,在python的实现中,都是通过名字字符串进行索引的,关键字字符串都是用qstr类型,qstr类型的字符串直接用“MP_QSTR_”前缀,并且不用双引号引起来。这在编译过程中,会通过编译主机python脚本自动提取这些qstr的实际字符串,这个操作类似于c编译器工具链中的预处理环节。
“.print”指定的函数,是在用户在python中调用print(Pin)函数时,将Pin的实例化对象打印成字符串。
machine_pin对print函数的实现如下,打印“machine_pin_obj_t”结构体类型中“name”字段的字符串。
STATIC void machine_pin_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t kind) {(void)kind;const machine_pin_obj_t *self = MP_OBJ_TO_PTR(o);mp_printf(print, "Pin(%s)", qstr_str(self->name));
}
“machine_pin_obj_t”结构体类型是在“pin.h”文件中定义的,但我感觉也可以直接放在“machine_pin.h”文件中。在mimxrt的实现中,machine_pin_type和machine_pin_af_type都归在了“pin.h”中。至于这个“name”字段是么时候填进去的呢,我猜是new的时候填入的,让我们拭目以待。“mp_print_t”是打印的对象,可以是交互终端,也可以是log文件。
- “.call”指定的函数,对应的是Python中一个非常特殊的类的实例方法,即 __call__()。该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。在直白一点,就是将实例对象的名字本身也当成作为一个函数名。machine_pin对call函数的实现如下:
STATIC mp_obj_t machine_pin_obj_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {mp_arg_check_num(n_args, n_kw, 0, 1, false);machine_pin_obj_t *self = self_in;if (n_args == 0) {return MP_OBJ_NEW_SMALL_INT(mp_hal_pin_read(self));} else {mp_hal_pin_write(self, mp_obj_is_true(args[0]));return mp_const_none;}
}
结合此处的实现举例call()属性函数的用法:
pin1 = Pin() # 实例化一个Pin的对象pin1
print(pin1()) # 若参数数量为0个,则返回mp_hal_pin_read()函数读到的引脚值
pin1(0) #若参数为1个,则将传入的第一个参数args[0]作为mp_hal_pin_write()函数的参数,写入引脚指定电平值
参见:Python call()方法(详解版)
- “.make_new”指定的函数,是实例化一个类对象的操作过程。这个new函数会“无中生有”,在gc管理的内存区中动态申请一块内存,填入必要的属性信息,然后将这块内存的句柄(指针)返回给调用者。在下文中将详细介绍对应“mp_pin_make_new()”函数的实现,这个函数实现的传参过程比较有趣,可以以不同的编号方式指定对象,下文会有专门的篇幅进行详细介绍。
补充一句,如果按照看起来统一的命名规范,这里的“mp_pin_make_new()”函数名字应同其它属性函数一样使用“machine_pin_obj”前缀,称为“machine_pin_obj_make_new”
- “.locals_dict”是本类包含的局部关键字列表以及它们对应的功能(常量?函数?)。在python中有一个内建函数locals(),locals() 函数会以字典类型返回当前位置的全部局部变量。猜测这里的“locals_dict”是提供一个可返回的字典,同时用作模块内部可通过“.”算符继续访问的属性和方法资源。
参见:Python locals() 函数
machine_pin对locals_dict的实现如下:
STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = {// instance methods{ MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_off_obj) },{ MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) },{ MP_ROM_QSTR(MP_QSTR_low), MP_ROM_PTR(&machine_pin_off_obj) },{ MP_ROM_QSTR(MP_QSTR_high), MP_ROM_PTR(&machine_pin_on_obj) },{ MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) },{ MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pin_init_obj) },{ MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) },// class attributes{ MP_ROM_QSTR(MP_QSTR_board), MP_ROM_PTR(&machine_pin_board_pins_obj_type) },{ MP_ROM_QSTR(MP_QSTR_cpu), MP_ROM_PTR(&machine_pin_cpu_pins_obj_type) },// class constants{ MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(PIN_MODE_IN) },{ MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(PIN_MODE_OUT) },{ MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(PIN_MODE_OPEN_DRAIN) },{ MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(PIN_PULL_UP_100K) },{ MP_ROM_QSTR(MP_QSTR_PULL_UP_47K), MP_ROM_INT(PIN_PULL_UP_47K) },{ MP_ROM_QSTR(MP_QSTR_PULL_UP_22K), MP_ROM_INT(PIN_PULL_UP_22K) },{ MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(PIN_PULL_DOWN_100K) },{ MP_ROM_QSTR(MP_QSTR_PULL_HOLD), MP_ROM_INT(PIN_PULL_HOLD) },{ MP_ROM_QSTR(MP_QSTR_DRIVER_OFF), MP_ROM_INT(PIN_DRIVE_OFF) },{ MP_ROM_QSTR(MP_QSTR_POWER_0), MP_ROM_INT(PIN_DRIVE_POWER_0) }, // R0 (150 Ohm @3.3V / 260 Ohm @ 1.8V){ MP_ROM_QSTR(MP_QSTR_POWER_1), MP_ROM_INT(PIN_DRIVE_POWER_1) }, // R0/2{ MP_ROM_QSTR(MP_QSTR_POWER_2), MP_ROM_INT(PIN_DRIVE_POWER_2) }, // R0/3{ MP_ROM_QSTR(MP_QSTR_POWER_3), MP_ROM_INT(PIN_DRIVE_POWER_3) }, // R0/4{ MP_ROM_QSTR(MP_QSTR_POWER_4), MP_ROM_INT(PIN_DRIVE_POWER_4) }, // R0/5{ MP_ROM_QSTR(MP_QSTR_POWER_5), MP_ROM_INT(PIN_DRIVE_POWER_5) }, // R0/6{ MP_ROM_QSTR(MP_QSTR_POWER_6), MP_ROM_INT(PIN_DRIVE_POWER_6) }, // R0/7{ MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(1) },{ MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(2) },
“mp_rom_map_elem_t”顾名思义是存放在rom中的map元素类型,内部包含若干的“键值对”,左边为qstr类型的关键字字符串,右边对应资源。例如:“MP_QSTR_off”对应的是“off”关键字和函数“machine_pin_off_obj”,“MP_ROM_PTR()”宏特别指定了这是一个“PTR”指针;“MP_QSTR_IN”对应的是“IN”关键字和“PIN_MODE_IN”常量,“MP_ROM_INT()”宏特别指定了这是一个“INT”整数,“PIN_MODE_INT”的值也是在“pin.h”文件中定义的。
特别说明
在machine_pin.c文件中使用了一些宏操作,将一个函数或者一个数组封装成对象。切记,在python中,一切皆对象,函数、数组、变量甚至常量。
小如指针、常量:
MP_ROM_PTR(&machine_pin_off_obj)
MP_ROM_INT(1)
大如函数、数组:
MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_off_obj, machine_pin_off);
MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_init);
MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table);
它们的实现代码在“py/obj.h”文件中,实际上是定义了一个引用了当前资源的新的变量:
#define MP_DEFINE_CONST_FUN_OBJ_0(obj_name, fun_name) \const mp_obj_fun_builtin_fixed_t obj_name = \{{&mp_type_fun_builtin_0}, .fun._0 = fun_name}
#define MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name) \const mp_obj_fun_builtin_fixed_t obj_name = \{{&mp_type_fun_builtin_1}, .fun._1 = fun_name}
#define MP_DEFINE_CONST_FUN_OBJ_2(obj_name, fun_name) \const mp_obj_fun_builtin_fixed_t obj_name = \{{&mp_type_fun_builtin_2}, .fun._2 = fun_name}
#define MP_DEFINE_CONST_FUN_OBJ_3(obj_name, fun_name) \const mp_obj_fun_builtin_fixed_t obj_name = \{{&mp_type_fun_builtin_3}, .fun._3 = fun_name}
...#define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name) \const mp_obj_fun_builtin_var_t obj_name = \{{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name}
...#define MP_DEFINE_CONST_DICT(dict_name, table_name) \const mp_obj_dict_t dict_name = { \.base = {&mp_type_dict}, \.map = { \.all_keys_are_qstrs = 1, \.is_fixed = 1, \.is_ordered = 1, \.used = MP_ARRAY_SIZE(table_name), \.alloc = MP_ARRAY_SIZE(table_name), \.table = (mp_map_elem_t *)(mp_rom_map_elem_t *)table_name, \}, \}
至于其中“mp_type_fun_builtin_1”的定义,位于“py/objfun.c”文件中,是一个常量结构体,而不是一个类型:
STATIC mp_obj_t fun_builtin_1_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_1));mp_obj_fun_builtin_fixed_t *self = MP_OBJ_TO_PTR(self_in);mp_arg_check_num(n_args, n_kw, 1, 1, false);return self->fun._1(args[0]);
}const mp_obj_type_t mp_type_fun_builtin_1 = {{ &mp_type_type },.flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN,.name = MP_QSTR_function,.call = fun_builtin_1_call,.unary_op = mp_generic_unary_op,
};
“mp_type_fun_builtin_1”的“.call”指向“fun_builtin_1_call()”函数,从这个函数的实现代码可以看出,其中通过“mp_arg_check_num()”函数检查了参数,有效传入参数仅为1个。之后通过“self->fun._1()”函数执行MP_DEFINE_CONST_FUN_OBJ_1(obj_name, fun_name)执行其中的fun_name,而obj_name就是这里的“self”。
类似地,在“fun_builtin_2_call()”函数中,也过滤成有效参数为2个,“self->fun._2()”函数的传入参数为两个,args[0]和args[1]。
至于func._1和func._2,以及后面可能会经常看到的变参数或关键字参数的函数指针var和kw,其实都是函数指针,在结构体里定义成共享一块内存的union。在“py/obj.h”中有:
typedef struct _mp_obj_fun_builtin_fixed_t {mp_obj_base_t base;union {mp_fun_0_t _0;mp_fun_1_t _1;mp_fun_2_t _2;mp_fun_3_t _3;} fun;
} mp_obj_fun_builtin_fixed_t;typedef struct _mp_obj_fun_builtin_var_t {mp_obj_base_t base;uint32_t sig; // see MP_OBJ_FUN_MAKE_SIGunion {mp_fun_var_t var;mp_fun_kw_t kw;} fun;
} mp_obj_fun_builtin_var_t;
本节引用了不少代码,篇幅比较长,就到此为止吧。下节详细解释Pin模块中内部变量及成员函数的定义方法。
END
阅读micropyton源码-添加C扩展类模块(2)相关推荐
- c++ 多线程 类成员函数_为什么我说C/C++程序员都要阅读Redis源码之:通过Redis学习事件驱动设计
0. 为什么我说C/C++程序员都要阅读Redis源码 主要原因就是『简洁』.如果你用源码编译过Redis,你会发现十分轻快,一步到位.其他语言的开发者可能不会了解这种痛,作为C/C++程序员,如果你 ...
- 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作
系列文章目录 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作 高效阅读嵌入式源码系列二:understand阅读linux.uboot等源码 高效阅读嵌入式源码系列三:unde ...
- 阅读 ANDROID 源码的一些姿势
日常开发中怎么阅读源码 找到正确的源码 IDE是日常经常用的东西,Eclipse就不说了,直接从Android Studio(基于IntelliJ Community版本改造)开始. 我们平时的And ...
- 阅读 Android源码的一些姿势
日常开发中怎么阅读源码 找到正确的源码 IDE 是日常经常用的东西,Eclipse 就不说了,直接从 Android Studio(基于 IntelliJ Community 版本改造)开始. 我们平 ...
- 阅读Android源码的一些姿势
2019独角兽企业重金招聘Python工程师标准>>> 前面吐槽了 有没有必要阅读Android源码,后面觉得只吐槽不太好,还是应该多少弄点干货.需要说明的是,Android每个系统 ...
- Flink源码阅读-教你阅读Flink 源码
本文大纲 一.Flink 官方文档这么全面,为什么还要读 Flink 源码 读文档和读源码的目的是不一样的,就拿 Apache Flink 这个项目来说,如果你想知道 Flink 的使用功能,设计思想 ...
- 阅读react-redux源码 - 一
阅读react-redux源码 - 零 阅读react-redux源码 - 一 阅读react-redux源码(二) - createConnect.match函数的实现 阅读react-redux源 ...
- 大牛教你这样阅读android源码
当你去面试时,经常会被问到,你是否阅读过android系统源码?那系统源码该如何阅读呢? 下面,让我们来看看大牛们是如何阅读的(来自知乎的牛人们http://www.zhihu.com/questio ...
- 下载和阅读Android源码
目录 一.如何下载AOSP 1.全量下载 2.单个下载 目录结构 二.如何阅读AOSP 1.要阅读哪些源码 2.阅读源码的顺序和方式 2.1 阅读顺序 2.2 阅读方式 3.用什么工具来阅读 3.1 ...
最新文章
- C#用Tesseract进行OCR识别,可识别中英日韩所有语言
- java mp3播放器 无界面
- 你不知道的三种在for循环中使用setTimeout的方法
- 图像传感器与信号处理——自动曝光算法
- spark广播变量 和 累加器
- 九九乘法表Python+Java
- hdu 1512 Monkey King 左偏树
- 极兔正式入股百世快递
- 无线移动通信基础知识
- PHP在微博优化中的“大显身手”
- 学习红黑树过程中的个人总结
- QQ新版表情序号及对应
- 你的大四,推荐做的几件事 [英语专业女生的自白]
- 5G 第五代移动通信系统你知多少?
- 5个超棒的自我提升App
- unl文件导入orcle数据库
- Leetcode 1628. Design an Expression Tree With Evaluate Function [Python]
- 彻底弄透Java处理GMT/UTC日期时间
- vue 使用emoji表情包
- 联通宽带在停电以后断网,重启猫还是没网,可能是设置出问题了