2.2.6 Node addons

492 阅读3分钟

1. C++ 插件

1.1 前端成长曲线:

刚参加工作的程序员都倾向做后端开发,大家都觉得前端比较简单,后期成长曲线比较缓慢。
能在日常工作做项目中,就可以成长当然最好了,进入一个好的平台对成长帮助很大。但是很多人就没那么幸运了,这时候更多时候需要我们做一个有心人,持续学习。
底层基础很重要,计算机基础还是需要经常查看。

image.png

1.2 万物归于c:

  • 高级语言都是运行在操作系统之上,本身只能运行在用户态。
  • 操作系统管理计算机的资源,运行在内核态,操作系统是c写的,也就是c/c这样的高级语言可以直接与操作系统交互。
  • 高级语言需要使用系统资源,只能通过系统调用。
  • 在node、java我们都可以看到native方法,也就是c写的,除了c语言执行比较快,主要还是它们不能直接系统调用
  • node、java虚拟机本质也是操作系统一个应用程序,具备解释执行node、java语言

1.3 node与c

  • Native Addon

一个 Native Addon 在 Nodejs 的环境里就是一个二进制文件, 这个文件是由低级语言, 比如 C 或 C++实现, 我们可以像调用其他模块一样 require() 导入 Native Addon

Native Addon 与其他.js 的结尾的一样, 会暴露出 module.exports 或者 exports 对象, 这些被封装到 node 模块中的文件也被成为 Native Module(原生模块) .

  • ABI Application Binary Interface 应用二进制接口

Native Addon 与 Nodejs 进行通信的桥梁. ABI 定义了二进制文件(尤其是so文件)如何运行在相应的系统平台上,从使用的指令集,内存对其到可用的系统函数库。简单来说,通过abi接口,c/c++ 文件可以转化为运行在各种平台上的so文件。

  • 插件

是用 C++ 编写的动态链接共享对象

Nodejs 可以动态加载 C 和 C++的 DLL 文件, 并且在 js 程序中使用其 API

c++插件提供了 JavaScript 和 C/C++ 库之间的接口。require() 函数可以将插件加载为普通的 Node.js 模块

实现插件有三种选择:Node-API、nan 或直接使用内部 V8、libuv 和 Node.js 库

  • 构建原生插件的 API

    • node-addon-api
#include <napi.h>
    • Node-API
#include <node_api.h>

2.插件编写

  • 需要全局安装yarn global add node-gyp, 因为还依赖于 Python, (GYP 全称是 Generate Your Project, 是一个用 Python 写成的工具)
  • 构建命令: node-gyp configure build

  • binding.gyp
  • c ++ 实现
  • js引用

2.1 binding.gyp

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "addon.cc" ]
    }
  ]
}

2.2 c ++ 实现

// addon.cc
#include <node.h>

namespace demo {

using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

// 这是 "add" 方法的实现
// 输入参数使用
// const FunctionCallbackInfo<Value>& args 结构传入
void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  // 检查传入的参数数量。
  if (args.Length() < 2) {
    // 抛出传回 JavaScript 的错误
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate,
                            "Wrong number of arguments").ToLocalChecked()));
    return;
  }

  // 检查参数类型
  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate,
                            "Wrong arguments").ToLocalChecked()));
    return;
  }

  // 执行操作
  double value =
      args[0].As<Number>()->Value() + args[1].As<Number>()->Value();
  Local<Number> num = Number::New(isolate, value);

  // 设置返回值
  // (使用传入的 FunctionCallbackInfo<Value>&)
  args.GetReturnValue().Set(num);
}

void Init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "add", Add);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init)

}  

2.3 js引用

调用 node-gyp build 命令生成编译后的 addon.node 文件。 这将被放入 build/Release/ 目录。

const addon = require('./build/Release/addon');
console.log('This should be eight:', addon.add(3, 5));

关于打包的跨平台

通常在发布模块的时候, 不会把build文件夹算在内, 但是.node文件是放在里面的. 而且.node文件之前说了, 依赖于系统和架构, 如果是使用 macOS 打包的.node肯定是不能在 windows 上使用的. 那么怎么实现兼容性呢? 没错, 每次在用户安装的时候都重新按照对应硬件配置build 一遍, 也就是使用node-gyp rebuild

参考链接:
nodejs.cn/api/addons.…
juejin.cn/post/684490…

欢迎关注我的前端自检清单,我和你一起成长