阅读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)相关推荐

  1. c++ 多线程 类成员函数_为什么我说C/C++程序员都要阅读Redis源码之:通过Redis学习事件驱动设计

    0. 为什么我说C/C++程序员都要阅读Redis源码 主要原因就是『简洁』.如果你用源码编译过Redis,你会发现十分轻快,一步到位.其他语言的开发者可能不会了解这种痛,作为C/C++程序员,如果你 ...

  2. 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作

    系列文章目录 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作 高效阅读嵌入式源码系列二:understand阅读linux.uboot等源码 高效阅读嵌入式源码系列三:unde ...

  3. 阅读 ANDROID 源码的一些姿势

    日常开发中怎么阅读源码 找到正确的源码 IDE是日常经常用的东西,Eclipse就不说了,直接从Android Studio(基于IntelliJ Community版本改造)开始. 我们平时的And ...

  4. 阅读 Android源码的一些姿势

    日常开发中怎么阅读源码 找到正确的源码 IDE 是日常经常用的东西,Eclipse 就不说了,直接从 Android Studio(基于 IntelliJ Community 版本改造)开始. 我们平 ...

  5. 阅读Android源码的一些姿势

    2019独角兽企业重金招聘Python工程师标准>>> 前面吐槽了 有没有必要阅读Android源码,后面觉得只吐槽不太好,还是应该多少弄点干货.需要说明的是,Android每个系统 ...

  6. Flink源码阅读-教你阅读Flink 源码

    本文大纲 一.Flink 官方文档这么全面,为什么还要读 Flink 源码 读文档和读源码的目的是不一样的,就拿 Apache Flink 这个项目来说,如果你想知道 Flink 的使用功能,设计思想 ...

  7. 阅读react-redux源码 - 一

    阅读react-redux源码 - 零 阅读react-redux源码 - 一 阅读react-redux源码(二) - createConnect.match函数的实现 阅读react-redux源 ...

  8. 大牛教你这样阅读android源码

    当你去面试时,经常会被问到,你是否阅读过android系统源码?那系统源码该如何阅读呢? 下面,让我们来看看大牛们是如何阅读的(来自知乎的牛人们http://www.zhihu.com/questio ...

  9. 下载和阅读Android源码

    目录 一.如何下载AOSP 1.全量下载 2.单个下载 目录结构 二.如何阅读AOSP 1.要阅读哪些源码 2.阅读源码的顺序和方式 2.1 阅读顺序 2.2 阅读方式 3.用什么工具来阅读 3.1 ...

最新文章

  1. C#用Tesseract进行OCR识别,可识别中英日韩所有语言
  2. java mp3播放器 无界面
  3. 你不知道的三种在for循环中使用setTimeout的方法
  4. 图像传感器与信号处理——自动曝光算法
  5. spark广播变量 和 累加器
  6. 九九乘法表Python+Java
  7. hdu 1512 Monkey King 左偏树
  8. 极兔正式入股百世快递
  9. 无线移动通信基础知识
  10. PHP在微博优化中的“大显身手”
  11. 学习红黑树过程中的个人总结
  12. QQ新版表情序号及对应
  13. 你的大四,推荐做的几件事 [英语专业女生的自白]
  14. 5G 第五代移动通信系统你知多少?
  15. 5个超棒的自我提升App
  16. unl文件导入orcle数据库
  17. Leetcode 1628. Design an Expression Tree With Evaluate Function [Python]
  18. 彻底弄透Java处理GMT/UTC日期时间
  19. vue 使用emoji表情包
  20. 联通宽带在停电以后断网,重启猫还是没网,可能是设置出问题了

热门文章

  1. 江阴工控机服务器维修,西门子工控机上电进不了系统界面维修
  2. 华为光伏usb适配器_颜值控的好选择—YOGA USBC 65W电源适配器
  3. 卡尔曼滤波(联邦、一致性卡尔曼滤波)
  4. 万物皆可造的 3D 打印,这次盯上了森林
  5. Android 开发判断用户是否安装微信和QQ并跳转过去
  6. 基于大数据做文本分析
  7. 信号与系统 一到五章 整理笔记
  8. CSS样式控制鼠标移动到字体上,字体下出现下划线
  9. MySQL主从数据不一致,怎么办?
  10. 十万美元的悬赏——互联网梅森素数大搜索