Node-FFI 不得不数说的原理
自己在实际开发中总结的东西
从计算机基础开始讲起
线程, 进程, 协程
- 线程: 操作系统能够进行运算调度的最小单位,包含在进程之中,是进程中的实际运作单位,顺序执行
- 进程: 程序的基本执行实体,包含 1 或多个线程
- 协程: 非操作系统调度,由用户手动调度切换上下文的程序组件
- 相关链接
- 线程
- libuv
函数调用约定, 指针, (动态/静态)链接库
- 指针: 所有数据类型都包含对应的指针类型,其内容是指向的变量的地址,函数本身就是编译后的一块内存,所以函数本身就是指针
- 函数调用约定: 不同CPU不同操作系统的底层实现中,调用一个函数都是靠操作栈来实现,但是不同的环对栈的顺序与存储的大小约定不同,这些约定称为函数调用约定
- 函数调用约定
- 链接库
符号表
回到 Node.js 的部分
Demo 运行截图(N-API 为实验性特性所以会有一个警告):

其中第三种方案其实是借助 libuv
的接口实现,此前这种方式仅仅是因为
Node.js 使用了 uv_default_loop
,不过在 N-API 中被官方支持了:

- Event Loop, GC
- 简单来说: 一个分阶段的线程,通常我们称为 JS 主线程,V8 中使用
uv_default_loop
实现
- GC: 即垃圾回收,垃圾回收是相对于我们编译器编译阶段可见的上下文为准的,即一个动态链接库的对象生命周期对调用它的程序不可见
Node-FFI: 令人兴奋的跳跃
从载入动态库说起
a. 其实大部分编译型语言是具备载入动态库的能力的,因为操作系统已经提供了相关的封装,但是必须是编译前定义好被调用函数结构、函数调用规则等
b. 动态类型语言是运行在 VM 之上,而此时的 VM 已经是是编译过的二进制模块,已经不具备上文所说的特性
libffi: Node-FFI 的核心
a. 优势: 可以动态定义函数的调用方法,可以动态定绑定函数实体到指针
b. 应用: OpenJDK, Dalvik, CPython
等非常著名的引擎: libffi
c. 实现:
特性 |
实现方式 |
实现语言 |
定义函数 |
通过定义 ffi_cif 结构体描述函数,并通过模拟函数调用方式实现调用 |
inline asm |
绑定函数 |
通过定义 ffi_closure 绑定 ffi_cif 到指定函数并返回一个函数指针,当调用这个指针,指针指向的函数通过 ffi_cif 结构体的定义模拟取栈的操作并传入被绑定的函数 |
inline asm |
Node-FFI: 函数回调的实现
题外话