返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Node中完整的 node addon 实现流程
  • 357
分享到

Node中完整的 node addon 实现流程

2024-04-02 19:04:59 357人浏览 安东尼
摘要

目录背景介绍为什么要写 node addonnode addon 是什么addon 实现方式的变迁Chrome V8 apiNAN 时代符合 ABI 的 N-API编码阶段如何写出正

背景介绍

为什么要写 node addon

试想这样一种场景:我们想在 js 层实现某个业务场景,但是这套业务逻辑已经有存在的 c++ 版本了,这个时候我们有两个选择

  • 重新实现一套在 JS 版本的业务场景
  • 使用 node addon 桥接 C++ 版本代码

对比以上两种方案,显然,使用 addon 不用去写过重的业务逻辑,是一种成本更低的方案

node addon 是什么

  • node addon,即为 node 插件 / 扩展,插件是用 C++ 编写的动态链接共享对象。
    • 动态链接共享对象,即动态链接库
    • 链接库:库文件的二进制版本,即将库文件进行编译、打包操作后得到二进制文件,无法独立运行,必须等待其他程序调用才会被载入内存中。
    • 静态链接:无论缺失的地址位于其他目标文件还是链接库,链接库都会逐个找到各目标文件中缺失的地址。采用此链接方式生成的可执行文件,可以独立载入内存运行。
    • 动态链接:链接器先从所有目标文件中找到部分缺失的地址,然后将所有目标文件组织成一个可执行文件。这样生成的可执行文件,仍缺失部分函数和变量地址,待文件执行时,需连同所有的链接库文件一起载入内存,再由链接器完成剩余的地址修复工作,才能正常执行
    • 静态链接库:在生成可执行文件之前完成所有链接操作,使用的库文件是静态链接库,后缀名 .a .lib
    • 动态链接库:将部分链接操作推迟到程序执行时才进行,使用的库文件是动态链接库,后缀名:.so .dll .dylib
  • 插件提供了 javascript 和 C/C++ 库之间的接口。
  • require 函数可以将插件加载为普通的 node.js 模块。
  • 通俗点来讲,是一个能够桥接 c++ 和 js 的中间转换层

可通过 NODE-APINAN、或者使用底层 v8 库来实现【官方建议使用 NODE-API

  • node-api:构建原生插件的 api,独立于 JS 运行时,此 API 是跨 Node.js  版本稳定的应用程序二进制接口,它旨在将插件与底层 JavaScript 引擎中的更改隔离开来,并允许为一个主要版本编译的模块
  • nan(Native Abstractions for Node.js):是一个 Node.js 原生模块抽象接口集。它提供了一套 API
  • 底层 V8:就是我们熟悉的 Chrome V8

addon 实现方式的变迁

Chrome V8 API

1、是啥:即使用 Node 自身各种 API 以及 Chrome V8 的 API

2、存在的问题

这些写好的代码只能在特定的 Node 版本下编译,因为其中各种 API、函数声明等的变化会很大,举个例子

Handle<Value> Echo(const Arguments& args);    // 0.10.x
void Echo(FunctionCallbackInfo<value>& args); // 6.x

NAN 时代

1、是啥:

  • Native Abstractions for Node.js,即 Node.js 原生模块抽象接口集
  • 代码只需要随着 NAN 的升级做改变,它会帮我们兼容各个版本

2、存在的问题

  • 一次写好的代码在不用版本的 Node.js 下也需要重新编译,如果版本不符合,Node.js 就无法正常载入一个 C++ 扩展
    • NAN 的封装方式是使用了一堆宏,在不同的 Node 版本下使用不同的宏变量、函数等,所以针对用户使用不同的 Node 版本,是需要进行重新编译的

符合 ABI 的 N-API

1、是啥

  • 自从 2017 年 Node.js v8.0.0 发布之后,Node.js 推出了全新的用于开发 C++ 原生模块的接口-> N-API
  • 与 NAN 相比,它把 Node.js 的所有底层数据结构全部黑盒化,抽象成 N-API 中的接口;不同版本的 Node.js 使用同样的接口,这些接口稳定且 ABI 化。只要 ABI 版本号一致,编译好的 C++ 扩展就可以直接使用,而不需要重新编译
    • ABI 化:(Application Binary Interface)应用程序二进制接口;可以理解为一种约定,是 API 的编译版本;ABI 允许编译好的目标代码在使用兼容 ABI 的系统中无需改动就能运行;一套完整的 ABI 可以让程序在所有支持该 ABI 的系统上运行,无需对程序进行修改
    • API:(Application programming interface),应用编程接口
  • 主要收益:消除了 nodejs 版本之间的差异

2、N-API 的使用姿势

  • 提供头文件 node_api.h
  • 使用文档:nodejs.org/api/n-api.h…

3、node-addon-api 是啥?

  • 可以理解为是对 N-API 更进一步的封装,更加便于我们开发
  • 举个例子
// N-API
#include <assert.h> 
#include <node_api.h> 
static napi_value Method(napi_env env, napi_callback_info info) { 
  napi_status status; 
  napi_value world; 
  status = napi_create_string_utf8(env, "world", 5, &world); 
  assert(status == napi_ok); 
  return world; 
} 
 
#define DECLARE_NAPI_METHOD(name, func)                                        \ 
  { name, 0, func, 0, 0, 0, napi_default, 0 } 
 
static napi_value Init(napi_env env, napi_value exports) { 
  napi_status status; 
  napi_property_descriptor desc = DECLARE_NAPI_METHOD("hello", Method); 
  status = napi_define_properties(env, exports, 1, &desc); 
  assert(status == napi_ok); 
  return exports; 
} 
 
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) 



// node-addon-api
#include <napi.h> 
 
Napi::String Method(const Napi::CallbackInfo& info) { 
  Napi::Env env = info.Env(); 
  return Napi::String::New(env, "world"); 
} 
 
Napi::Object Init(Napi::Env env, Napi::Object exports) { 
  exports.Set(Napi::String::New(env, "hello"), 
              Napi::Function::New(env, Method)); 
  return exports; 
} 
 
NODE_API_MODULE(hello, Init) 

编码阶段

如何写出正确的 addon 逻辑

  • demo.h

demo.cc 

1、熟悉 C++ 基础语法

宏的定义:#define 是定义一个宏的指令(预编译指令),它用来将一个标识符定义为一个字符串,该标识符被称为宏,被定义的字符串被称为替换文本,

  • 简单的宏定义和带参数的宏定义
  • 当宏出现在一个文件中时,在该文件后续出现的所有宏都将被替换为 《替换文本》
  • 常用于条件编译情况下,比如版本号的不同而编译不同的逻辑
// 简单的宏定义
#define PI 1415926  // 宏名 字符串

// 带参数的宏定义
#define A(x) x   // 宏名(参数表) 宏体
    • 公有继承、私有继承、保护继承
      • 公有继承:继承父类 public 和 protected 的方法和变量,不能访问 private
      • 私有继承:继承父类的 public 和 protected 的方法和变量作为私有成员,不能被该类的子类再次访问
      • 保护继承:继承父类 public 和 protected 成员作为保护成员
    • 公有、私有、受保护的成员
      • public:可被子类继承或在类外内访问
      • private:仅限类内部使用,不可被继承和访问
      • protected: 可被子类继承,但不能在类外访问
// test.h
class Test : public B {  // private || protected
	public:
  private:
  protected:
  	int pro = 1;
}
// 类外
#include "test.h"
Test test;   // 实例化 Test 类
std::cout << test.pro << std::endl;  // error -> 不可在类外被访问
  • 构造函数和析构函数
    • 构造函数:类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。类似于 JS 中的 constructor; 可自己实现,也可使用编译器生成的默认构造函数,即与类名相同的函数;
    • 析构函数:类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
  • 虚函数、纯虚函数是啥
    • virtual:虚函数关键字,子类可选择自己实现或使用父类原有方法
    • = 0:纯虚函数关键字,子类必须自己实现,如果不实现,编译阶段将会报错
    • override:是一个覆盖虚函数的标识符

2、熟悉 addon 语法

1、如何让 js require?无后缀情况下的 .js -> .JSON -> .node

  • 在 js 中,使用 commonJs 语法即可让该模块被其他模块 require,addon 中则也是类似的想法
  • 在 addon 中,提供了 NODE_API_MODULE 宏方法,用这个方法即可实现外部 require 效果,方法接收两个参数,即模块名字和导出的方法
  • 具体实现?
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
  return Link::Init(env, exports);
}
NODE_API_MODULE(link, InitAll);

2、定义一个类以及注册方法

  • 在 js 中的效果即为 class A { //.... }
Napi::Object Link::Init(Napi::Env env, Napi::Object exports) {
  Napi::Function func =
      DefineClass(
    			env, "Demo",
          {
            InstanceMethod("add", &Demo::Add),
          }
  	);

  auto constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("Demo", func);
  return exports;
}

3、函数的接收参数

  • 在 addon 中
    • 接收多个参数:
      • 定义好每个参数的类型接收
      • 统一在 CallbackInfo 中接收:GitHub.com/nodejs/node…
    • 接收一个对象
      • 也是从 info[0] 中去拿到这个对象,然后用 object.Get(key) 方法拿到对应的参数
// 1、定义好参数接收
Napi::Object Link::Init(Napi::Env env, Napi::Object exports) {}

// 2、在 CallbackInfo 中接收
Napi::Value Link::TagSync(const Napi::CallbackInfo &info) {
  string bizId = info[0].As<Napi::String>();
  auto tags = info[1].As<Napi::Array>();
}
ApplicationInfo applicationInfo = ParseValueAsApplicationInfo(info[0]);

kwai::link::ApplicationInfo ParseValueAsApplicationInfo(Napi::Value value) {
  kwai::link::ApplicationInfo applicationInfo;
  auto object = value.As<Napi::Object>();

  applicationInfo.app_id = GetObjectValueAsInt32(object, "appId");
  return applicationInfo;
}

int32_t GetObjectValueAsInt32(Napi::Object object, std::string keyName) {
  if (object.Get(keyName).IsNumber()) {
    return object.Get(keyName).ToNumber().Int32Value();
  }

  return 0;
}

4、函数的返回值

  • 类型约束:在函数前约束,类型可写 addon 类型或者原生 C++ 类型
  • 和 js 一样,写个 return 就可以了

5、env

  • 是什么
    • 可以理解为是 node addon 运行时的请求环境
    • Env 对象通常由 Node.js 运行时或 node-addon-api 基础结构创建和传递
  • 怎么用:github.com/nodejs/node…
    • 类型声明:Napi::Env
    • 取值:info.Env()
  • 为什么需要构建这个环境

3、熟悉业务逻辑

有了上面两个知识储备后,下一步我们就要根据实际的业务场景,去写 addon 逻辑了

如何向外暴露方法

这个例子可结合上面的 demo.cc 和 demo.h 来一起看

Value runSimpleAsyncWorker(const CallbackInfo& info) {
  int runTime = info[0].As<Number>();
  Function callback = info[1].As<Function>();
  SimpleAsyncWorker* asyncWorker = new SimpleAsyncWorker(callback, runTime);
  asyncWorker->Queue();
  std::string msg =
      "SimpleAsyncWorker for " + std::to_string(runTime) + " seconds queued.";
  return String::New(info.Env(), msg.c_str());
};

Object Init(Env env, Object exports) {
  exports["runSimpleAsyncWorker"] = Function::New(
      env, runSimpleAsyncWorker, std::string("runSimpleAsyncWorker"));
  return exports;
}

NODE_API_MODULE(addon, Init)

编译阶段

编译流程

使用 node-gyp 来构建,最终产出 .node 文件

1、第一步安装所需依赖

npm i node-gyp -g

2、第二步配置 binding.gyp

{
    "targets": [
            {
                "target_name": "demo",
                "cflags!": [ "-fno-exceptions" ],
                "cflags_cc!": [
                        "-Wc++11-extensions"
                ],
                "sources": [         
        "./src/simple_async_worker.cc",
                        "./src/addon.cc",
                ],
                "include_dirs": [
                        "<!@(node -p \"require('node-addon-api').include\")",
                        "./",
                ],
                'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
                "conditions": [
                        [
                                'OS=="Mac"',
                                {
                                        "link_settings": {
                    "libraries": [
                        # 可引入一个静态库
                    ]
                                        },
                                        "xcode_settings": {
                                                "OTHER_CFLAGS": [ "-std=c++17", "-fexceptions", ],
                                        },
                                        'defines': [
                                                'MACOS',
                                        ],
                                        "cflags_cc": [
                                                "-std=c++17"
                                        ]
                                }
                        ],
                ]
        },
],
}

3、执行 node-gyp rebuild 命令即可生成 require 方法可引入的 .node 文件

结语

根据以上步骤,可实现一个极为简单的 node addon 扩展,但是在实际开发过程中,会面临更多的问题解决,欢迎讨论~

到此这篇关于Node中完整的 node addon 实现流程的文章就介绍到这了,更多相关node addon 流程内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Node中完整的 node addon 实现流程

本文链接: https://lsjlt.com/news/168234.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Node中完整的 node addon 实现流程
    目录背景介绍为什么要写 node addonnode addon 是什么addon 实现方式的变迁Chrome V8 APINAN 时代符合 ABI 的 N-API编码阶段如何写出正...
    99+
    2024-04-02
  • AndroidService完整实现流程分析
    目录前言一.APP侧启动Service1.1 前台和后台启动1.2startServiceCommon二.系统侧分发处理Service的启动逻辑2.1 AMS接受启动service的...
    99+
    2023-01-02
    Android Service Android Service实现原理
  • node环境执行js文件的完整步骤
    目录1、JavaScript代码的执行2、Node的REPL3、Node程序执行参数传递总结1、JavaScript代码的执行 目前知道的两种方式有两种: 将代码交给浏览器(在浏览器...
    99+
    2023-02-03
    node 执行js文件 nodejs 可执行文件 node执行js文件的命令是什么
  • Node中的进程和线程怎么实现
    这篇文章主要介绍了Node中的进程和线程怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Node中的进程和线程怎么实现文章都会有所收获,下面我们一起来看看吧。一、进程和线程1.1、专业性文字定义进程(Pr...
    99+
    2023-07-04
  • Node多进程的实现方法
    目录1. child_process 模块2. spawn3. fork4. exec和execFile5. 各方法之间的比较5.1 spawn和execFile5.2 execFi...
    99+
    2022-11-13
    Node 多进程 Node 多进程通信
  • 如何实现node可读流之流动模式
    这篇文章将为大家详细讲解有关如何实现node可读流之流动模式,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。node的可读流基于事件可读流之流动模式,这种流动模式会有一个&...
    99+
    2024-04-02
  • Node中的进程间通信怎么实现
    这篇“Node中的进程间通信怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Node...
    99+
    2024-04-02
  • Node中的可读流是什么
    这篇文章主要介绍了Node中的可读流是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Node中的可读流是什么文章都会有所收获,下面我们一起来看看吧。1. 基本概念1.1. 流的历史演变流不是 Nodejs ...
    99+
    2023-07-05
  • 一文聊聊Node中的stream(流)
    流是用于在 Node.js 中处理流数据,也就是连续字节的抽象接口。 流有 4 种基本类型,本篇文章主要介绍其中两种 —— 可读流和可写流。可读的(Readable)我们可以通过 fs.createReadStream() 创建一个可读流 ...
    99+
    2023-05-14
    stream node nodejs​
  • 深入浅析Node中的Stream(流)
    和上面的示例对比起来,我们发现一个流同时面向生产者和消费者服务的时候我们会选择 Duplex,当只是对数据做一些转换工作的时候我们便会选择使用 Tranform背压问题什么是背压背压问题来源于生产者消费者模式中,消费者处理速度过慢比如说,我...
    99+
    2023-05-14
    前端 Node.js
  • node中Stream流的详细介绍
    目录一、是什么二、种类双工流双工流三、应用场景get请求返回文件给客户端文件操作一些打包工具的底层操作一、是什么 流(Stream),是一个数据传输手段,是端到端信息交换的一种方式,...
    99+
    2024-04-02
  • node的中间件怎么实现
    这篇文章主要介绍“node的中间件怎么实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“node的中间件怎么实现”文章能帮助大家解决问题。 ...
    99+
    2024-04-02
  • 浅析node中间件及实现一个简单的node中间件
    目录一、是什么二、封装token校验日志模块koa-bodyparserkoa-static三、总结一、是什么 中间件(Middleware)是介于应用系统和系统软件之间的一类软件,...
    99+
    2024-04-02
  • Node中的streams流的具体使用
    目录Node中的streams流转换流创建对象模式最后Node中的streams流 streams流是Node中的最好的特性之一。它在我们的开发过程当中可以帮助我们做很多事情。比如通...
    99+
    2024-04-02
  • node中http模块的使用及执行流程
    在node中http有什么作用 http这个模块的职责就是帮你创建编写服务器 执行流程  1. 加载http模块 const http = require('htt...
    99+
    2024-04-02
  • 一文聊聊Node中的可读流
    本篇文章带大家解读一下Node.js流源码,深入了解下Node可读流,看看其基本原理、使用方法与工作机制,希望对大家有所帮助!1. 基本概念1.1. 流的历史演变流不是 Nodejs 特有的概念。 它们是几十年前在 Unix 操作系统中引入...
    99+
    2023-05-14
    可读流 node
  • Node中的可读流和可写流实例代码分析
    这篇“Node中的可读流和可写流实例代码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Node中的可读流和可写流实例代码...
    99+
    2023-07-05
  • Node事件循环的流程是什么
    这篇文章主要讲解了“Node事件循环的流程是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Node事件循环的流程是什么”吧!我们都知道目前我们用的应用程...
    99+
    2024-04-02
  • Node的多进程服务如何实现
    这篇文章主要介绍“Node的多进程服务如何实现”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Node的多进程服务如何实现”文章能帮助大家解决问题。我们现在已经知道了Node是单线程运行的,这表示潜在...
    99+
    2023-07-01
  • node中如何实现RPC通信
    本篇内容主要讲解“node中如何实现RPC通信”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“node中如何实现RPC通信”吧!什么是RPC?RPC:Remote Procedure Call(远...
    99+
    2023-07-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作