Node-FFI 不得不数说的原理

自己在实际开发中总结的东西

从计算机基础开始讲起
  • 线程, 进程, 协程

    • 线程: 操作系统能够进行运算调度的最小单位,包含在进程之中,是进程中的实际运作单位,顺序执行
    • 进程: 程序的基本执行实体,包含 1 或多个线程
    • 协程: 非操作系统调度,由用户手动调度切换上下文的程序组件
    • 相关链接
      1. 线程
      1. libuv
  • 函数调用约定, 指针, (动态/静态)链接库

    • 指针: 所有数据类型都包含对应的指针类型,其内容是指向的变量的地址,函数本身就是编译后的一块内存,所以函数本身就是指针
    • 函数调用约定: 不同CPU不同操作系统的底层实现中,调用一个函数都是靠操作栈来实现,但是不同的环对栈的顺序与存储的大小约定不同,这些约定称为函数调用约定
      1. 函数调用约定
    • 链接库
  • 符号表

回到 Node.js 的部分

Demo 运行截图(N-API 为实验性特性所以会有一个警告):

其中第三种方案其实是借助 libuv 的接口实现,此前这种方式仅仅是因为

Node.js 使用了 uv_default_loop,不过在 N-API 中被官方支持了:

  • Event Loop, GC
    • 简单来说: 一个分阶段的线程,通常我们称为 JS 主线程,V8 中使用 uv_default_loop 实现
    • GC: 即垃圾回收,垃圾回收是相对于我们编译器编译阶段可见的上下文为准的,即一个动态链接库的对象生命周期对调用它的程序不可见
Node-FFI: 令人兴奋的跳跃
  1. 从载入动态库说起

    a. 其实大部分编译型语言是具备载入动态库的能力的,因为操作系统已经提供了相关的封装,但是必须是编译前定义好被调用函数结构、函数调用规则等

    b. 动态类型语言是运行在 VM 之上,而此时的 VM 已经是是编译过的二进制模块,已经不具备上文所说的特性

  2. libffi: Node-FFI 的核心

    a. 优势: 可以动态定义函数的调用方法,可以动态定绑定函数实体到指针

    b. 应用: OpenJDK, Dalvik, CPython 等非常著名的引擎: libffi

    c. 实现:

特性 实现方式 实现语言
定义函数 通过定义 ffi_cif 结构体描述函数,并通过模拟函数调用方式实现调用 inline asm
绑定函数 通过定义 ffi_closure 绑定 ffi_cif 到指定函数并返回一个函数指针,当调用这个指针,指针指向的函数通过 ffi_cif 结构体的定义模拟取栈的操作并传入被绑定的函数 inline asm
Node-FFI: 函数回调的实现
题外话
  • 让我想想再说…