node.js c++拓展开发

news2024/9/17 4:37:41

初始化项目

1、下载源码

地址:https://nodejs.org/dist/v19.7.0/

node-v19.7.0.tar.gz 就是node 19.7.0的源码

2、创建文件

下载完后解压到文件夹中(这里我命名为 node-v19.7.0 ) 新建一个文件 binding.gyp

target_name就是你的模块名 以后用的时候就是 s = require('botton')

{
    "targets":[
        {
            "target_name":"botton",
            "sources":["src/botton.cc"]
        }
    ]
}

接着在 src 目录下创建 botton.cc 这个文件,开始写C++代码

#include <node.h>

namespace v8 {

void myFunc(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();  // 获取隔离实例
  Local<String> arg1 = args[0].As<String>();  // 获取第一个参数并转换为C++中的字符串类型
  Local<String> ret = String::NewFromUtf8(isolate, "Hello,")
                          .ToLocalChecked();  // 在C++中创建字符串类型"Hello,"
  ret = ret->Concat(isolate, ret, arg1);
  args.GetReturnValue().Set(ret);  // 设置函数的返回值
}

void init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", myFunc); // 导出hello方法,回调函数是myFunc
}

NODE_MODULE(addon, init)
}  // namespace v8
3、编译代码

node-gyp 的安装在上篇文章有讲

cd到 node-v19.7.0 目录下

node-gyp configure 生成项目文件

node-gyp build 编译

执行上面两步来编译写好的node.js拓展,或者直接执行 node-gyp rebuild (会自动执行clean、configure和build三个命令)

4、使用拓展
// 假设已经在node-v19.7.0目录下
node
// 可以不加.node后缀,导包的时候会自动去查找
s = require('/build/Release/botton.node')
s.hello('botton')
// 返回Hello,botton

常用方法介绍

void addFunc(const FunctionCallbackInfo<Value>& args) {
  Local<Number> arg1 = args[0].As<Number>(); // 类型转换成 C++ 中的 Number
  Local<Number> arg2 = args[1].As<Number>();
  double num1 = arg1->Value();
  double num2 = arg2->Value();
  printf("num1:%lf num2:%lf\n", num1, num2); // 相当于 console.log

  Local<Number> result = Number::New(args.GetIsolate(), num1 + num2); // 创建 Number

  args.GetReturnValue().Set(result); // 设置返回值
};

void testFunc(const FunctionCallbackInfo<Value>& args) {
  Local<String> arg =
      String::NewFromUtf8(args.GetIsolate(), "console.log('exec eval')")
          .ToLocalChecked(); // MaybeLocal 类型,需要确定能否转换,否则会出错,用ToLocalChecked转换
  Local<Script> script =
      Script::Compile(args.GetIsolate()->GetCurrentContext(), arg)
          .ToLocalChecked();
  script->Run(args.GetIsolate()->GetCurrentContext());
  Local<String> result =
      String::NewFromUtf8(args.GetIsolate(), "botton").ToLocalChecked();

  args.GetReturnValue().Set(result);
};

void testFunc1(const FunctionCallbackInfo<Value>& args) {

  Local<Script> script;
  Local<String> arg = args[0].As<String>();
  bool check =
      Script::Compile(args.GetIsolate()->GetCurrentContext(), arg)
          .ToLocal(&script); // 判断能否转换类型,避免报错
  if (check) {
    script->Run(args.GetIsolate()->GetCurrentContext());
    Local<String> result =
        String::NewFromUtf8(args.GetIsolate(), "botton").ToLocalChecked();
    args.GetReturnValue().Set(result);
  }
};

实现函数柯里化

接下来我们来整点好玩的,实现一个前端的高频考题,如何实现一个函数柯里化,定义如下:

add(1)(2)(3) // => 6
add(1, 2, 3) // => 6

我们会用到的一些技术点:

  • 如何在 c++ 函数中返回一个函数供 JS 调用
  • 如何让返回值既支持函数调用又支持取值操作
  • 如何处理非固定数量的参数(其实这个很简单了,从上边也能看出来,本身就是一个数组)
C++版本函数柯里化

代码如下:

#include <node.h>

namespace v8 {

// 用来覆盖 valueOf 实现的函数
void getValueOf(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  // 获取在创建 valueOf 函数的时候传入的result
  Local<Value> storedValue = args.Data();
  Local<Number> result;
  // 避免空指针情况
  if (!storedValue.IsEmpty() && storedValue->IsNumber()) {
    result = storedValue.As<Number>();
  } else {
    result = Number::New(isolate, 0);
  }
  args.GetReturnValue().Set(result);
};

void curryAdd(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = args.GetIsolate()->GetCurrentContext();

  // 获取我们下边在创建 curryAdd 函数的时候传入的 result
  Local<Value> storedValue = args.Data();
  double stored = 0;
  if (!storedValue.IsEmpty() && storedValue->IsNumber()) {
    stored = storedValue.As<Number>()->Value();
  }
  double result = stored;

  // 遍历传入的所有参数
  int len = args.Length();
  for (int index = 0; index < len; index++) {
    double arg = args[index]->ToNumber(context).ToLocalChecked()->Value();
    result += arg;
  }

  // 创建一个新的函数用于函数的返回值
  Local<Function> fn =
      Function::New(context, curryAdd, Number::New(isolate, result))
          .ToLocalChecked();
  fn->SetName(String::NewFromUtf8(isolate, "curryAdd").ToLocalChecked());

  Local<Function> getValueofFunc =
      Function::New(context, getValueOf, Number::New(isolate, result))
          .ToLocalChecked();
  getValueofFunc->SetName(
      String::NewFromUtf8(isolate, "valueOf").ToLocalChecked());
  fn->Set(context,
          String::NewFromUtf8(isolate, "valueOf").ToLocalChecked(),
          getValueofFunc);

  args.GetReturnValue().Set(fn);
}

void init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "curryAdd", curryAdd);
}
NODE_MODULE(addon, init);
}  // namespace v8

编译完成以后,再写一段简单的 JS 代码来调用验证结果即可:

const { curryAdd } = require('./build/Release/botton.node');

const fn = curryAdd(1, 2, 3);
const fn2 = fn(4);

console.log(fn.valueOf())     // => 6
console.log(fn2.valueOf())    // => 10
console.log(fn2(5).valueOf()) // => 15

然后可以讲一下上边列出来的三个技术点是如何解决的:

  • 如何在 c++ 函数中返回一个函数供 JS 调用
    • 通过 Function::New 创建新的函数,并将计算结果存入函数可以获取到的地方供下次使用
  • 如何让返回值既支持函数调用又支持取值操作
    • 通过 fn->Set 篡改 valueOf 函数并返回结果
  • 如何处理非固定数量的参数(其实这个很简单了,从上边也能看出来,本身就是一个数组)
    • 通过拿到 argsLength 来遍历获取
JS版本函数柯里化
function curryAdd(...Args) {
    let result = Args.reduce((acc, cur) => acc + cur, 0);
    let fn = function (...moreArgs) {
        return curryAdd(result,...moreArgs);
    }

    fn.valueOf = () => result;

    return fn;
}

const fn = curryAdd(1, 2, 3);
const fn2 = fn(4);

console.log(fn.valueOf());     // => 6
console.log(fn2.valueOf());    // => 10
console.log(fn2(5).valueOf()); // => 15

性能对比

JS版本冒泡排序

为了证明效率的差异,我们选择用一个排序算法来验证,采用了最简单易懂的冒泡排序来做,首先是 JS 版本的:

function bubbleSortJS (arr) {
  for (let i = 0, len = arr.length; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] < arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }

  return arr
}
C++版本冒泡排序

因为是一个 JS 的扩展,所以会涉及到数据类型转换的问题,代码如下:

#include <node.h>

namespace v8 {

void bubbleSort(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = args.GetIsolate()->GetCurrentContext();

  Local<Array> arr = args[0].As<Array>();
  int length = arr->Length(), i, j;
  Local<Array> newArr = Array::New(isolate, length);
  double* list = new double[length];
  for (i = 0; i < length; i++) {
    list[i] = arr->Get(context, i).ToLocalChecked().As<Number>()->Value();
  }

  double temp;
  for (i = 0; i < length; i++) {
    for (j = i + 1; j < length; j++) {
      if (*(list + i) < *(list + j)) {
        temp = *(list + i);
        *(list + i) = *(list + j);
        *(list + j) = temp;
      }
    }
  }

  for (i = 0; i < length; i++) {
    newArr->Set(context, i, Number::New(isolate, list[i]));
  }
  args.GetReturnValue().Set(newArr);
}

void init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "bubbleSort", bubbleSort);
}
NODE_MODULE(addon, init);
}  // namespace v8

编译完成以后,再写一段简单的 JS 代码来调用验证结果即可:

const { bubbleSort } = require('./build/Release/botton.node')

const arr = Array.from(new Array(1e3), () => Math.random() * 1e6 | 0)

console.time('c++')
bubbleSort(arr)
console.timeEnd('c++')

function bubbleSortJS (arr) {
  for (let i = 0, len = arr.length; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] < arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }

  return arr
}

console.time('js')
bubbleSortJS(arr)
console.timeEnd('js')

1,000 数据量的时候耗时差距大概在 5 倍左右,在 10,000 数据量的时候耗时差距大概在 0.8 倍左右。
也是简单的证实了在相同算法情况下 c++ 效率确实是会比 JS 高一些。

当然了,也通过上边的 bubbleSort 可以来证实另一个观点: 有更多的 C++ 版本的轮子可以拿来用
就比如上边的 bubbleSort 函数,可能就是一个其他的加密算法实现、SDK 封装,如果没有 node 版本,而我们要使用就需要参考它的逻辑重新实现一遍,但如果采用 C++ 扩展的方式,完全可以基于原有的 C++ 函数进行一次简单的封装就拥有了一个 node 版本的 函数/SDK。

参考资料:

https://juejin.cn/post/6858272800596197383

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LabVIEW FIFO详解

在LabVIEW的FPGA开发中&#xff0c;FIFO&#xff08;先入先出队列&#xff09;是常用的数据传输机制。通过配置FIFO的属性&#xff0c;工程师可以在FPGA和主机之间&#xff0c;或不同FPGA VIs之间进行高效的数据传输。根据具体需求&#xff0c;FIFO有多种类型与实现方式&#x…

SpringSecurity原理解析(二):认证流程

1、SpringSecurity认证流程包含哪几个子流程&#xff1f; 1&#xff09;账号验证 2&#xff09;密码验证 3&#xff09;记住我—>Cookie记录 4&#xff09;登录成功—>页面跳转 2、UsernamePasswordAuthenticationFilter 在SpringSecurity中处理认证逻辑是在UsernamePas…

iOS——线程安全、线程同步与线程通信

线程安全和线程同步 线程安全&#xff1a;如果你的代码所在的进程中有多个线程在同时运行&#xff0c;而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的&#xff0c;而且其他的变量的值也和预期的是一样的&#xff0c;就是线程安全的。 若每个…

18055 主对角线上的元素之和

### 思路 1. 输入一个3行4列的整数矩阵。 2. 计算主对角线上的元素之和。 3. 输出主对角线上的元素之和。 ### 伪代码 1. 初始化一个3行4列的矩阵 matrix。 2. 输入矩阵的元素。 3. 初始化一个变量 sum 为0&#xff0c;用于存储主对角线元素之和。 4. 遍历矩阵的行&#xff0c…

【Day08-IO-文件字节流】

File 1. 概述 File对象既可以代表文件、也可以代表文件夹。它封装的对象仅仅是一个路径名&#xff0c;这个路径可以存在&#xff0c;也可以不存在 构造器 说明 public File​(String pathname) 根据文件路径创建文件对象 public File​(String parent, String child) 根据…

vscode中使用go环境配置细节

1、在docker容器中下载了go的sdk 2、在/etc/profile.d/go.sh里填入如下内容: #!/bin/bashexport GOROOT=/home/ud_dev/go export PATH=$GOROOT/bin:$PATH 3、设置go env go env -w GOPROXY=https://goproxy.cn,direct go env -w GO111MODULE=on 4、重启这个容器,使得vscod…

DBAPI如何使用内存缓存

背景 在使用DBAPI创建API的时候&#xff0c;有时候SQL查询比较耗时&#xff0c;如果业务上对数据时效性要求不高&#xff0c;这种耗时的SQL可以使用缓存插件来将数据缓存起来&#xff0c;避免重复查询。 一般来说&#xff0c;可以使用redis memcache等缓存服务来存储缓存数据。…

活动|华院计算宣晓华受邀出席“AI引领新工业革命”大会,探讨全球科技的最新趋势

8月31日&#xff0c;“AI引领新工业革命”大会于上海图书馆圆满落幕。本次大会由TAA校联会和台协科创工委会联合主办&#xff0c;得到上海市台办、上海市台联、康师傅的大力支持。大会邀请了NVIDIA全球副总裁、亚太区企业营销负责人刘念宁&#xff0c;元禾厚望资本创始合伙人潘…

ispunct函数讲解 <ctype.h>头文件函数

目录 1.头文件函数 2.ispunct函数使用 小心&#xff01;VS2022不可直接接触&#xff0c;否则..!没有这个必要&#xff0c;方源一把抓住VS2022&#xff0c;顷刻 炼化&#xff01; 1.头文件函数 以上函数都需要包括头文件<ctype.h> &#xff0c;其中包括 ispunct 函数 #…

esp8266+sg90实现远程开关灯(接线问题)

1需要准备的设备 首先需要的设备 硬件&#xff1a;esp8266开发板和sg90舵机&#xff0c;还有公对母的杜邦线&#xff0c;以及一根usb程序下载线。 软件&#xff1a;Arduino IDE 因为sg90舵机接口是三个连着的&#xff0c;只能用公对母的杜邦线把三条信号线接到esp8266的不同引…

Linux驱动.之字符设备驱动框架,新内核框架,设备树(二)

第一篇比较长&#xff0c;第二篇&#xff0c;继续写&#xff0c;内容有重复 一、字符设备驱动框架 在用户空间中调用open&#xff0c;打开一个字符设备&#xff0c;执行流程如下&#xff1a;最终会执行chrdev中的ops对应的open函数。

【python计算机视觉编程——8.图像内容分类】

python计算机视觉编程——8.图像内容分类 8.图像内容分类8.1 K邻近分类法&#xff08;KNN&#xff09;8.1.1 一个简单的二维示例8.1.2 用稠密SIFT作为图像特征8.1.3 图像分类:手势识别 8.2贝叶斯分类器用PCA降维 8.3 支持向量机8.3.2 再论手势识别 8.4 光学字符识别8.4.2 选取特…

面试官:你是怎么处理vue项目中的错误的?

一、错误类型 任何一个框架&#xff0c;对于错误的处理都是一种必备的能力 在Vue 中&#xff0c;则是定义了一套对应的错误处理规则给到使用者&#xff0c;且在源代码级别&#xff0c;对部分必要的过程做了一定的错误处理。 主要的错误来源包括&#xff1a; 后端接口错误代…

网络原理之TCP协议(万字详解!!!)

目录 前言 TCP协议段格式 TCP协议相关特性 1.确认应答 2.超时重传 3.连接管理&#xff08;三次握手、四次挥手&#xff09; 三次握手&#xff08;建立TCP连接&#xff09; 四次挥手&#xff08;断开连接&#xff09; 4.滑动窗口 5.流量控制 6.拥塞控制 7.延迟应答…

(入门篇)JavaScript 网页设计案例浅析-简单的交互式图片轮播

网页设计已经成为了每个前端开发者的必备技能,而 JavaScript 作为前端三大基础之一,更是为网页赋予了互动性和动态效果。本篇文章将通过一个简单的 JavaScript 案例,带你了解网页设计中的一些常见技巧和技术原理。今天就说一说一个常见的图片轮播效果。相信大家在各类电商网…

使用vscode上传git远程仓库流程(Gitee)

目录 参考附件 git远程仓库上传流程 1&#xff0c;先将文件夹用VScode打开 2&#xff0c;第一次进入要初始化一下仓库 3&#xff0c;通过这个&#xff08;.gitignore&#xff09;可以把一些不重要的文件不显示 注&#xff1a;&#xff08;.gitignore中&#xff09;可屏蔽…

AI辅助编程里的 Atom Group 的概念和使用

背景 在我们实际的开发当中&#xff0c;一个需求往往会涉及到多个文件修改&#xff0c;而需求也往往有相似性。 举个例子&#xff0c;我经常需要在 auto-coder中需要添加命令行参数&#xff0c;通常是这样的&#xff1a; /coding 添加一个新的命令行参数 --chat_model 默认值为…

基于RAG和知识库的智能问答系统设计与实现

开局一张图&#xff0c;其余全靠编。 自己画的图&#xff0c;内容是由Claude根据图优化帮忙写的。 1. 引言 在当今数字化时代&#xff0c;智能问答系统已成为提升用户体验和提高信息获取效率的重要工具。随着自然语言处理技术的不断进步&#xff0c;特别是大型语言模型&#x…

Sonarqube 和 Sonar-scanner的安装和配置

SonarQube 简介 所谓sonarqube 就是代码质量扫描工具。 官网&#xff1a; https://www.sonarsource.com/sonarqube/ 在个人开发学习中用处不大&#xff0c; 我草&#xff0c; 我的代码质量这么高需要这玩意&#xff1f; 但是在公司项目中&#xff0c; 这个可是必须的&#x…

【高校主办,EI稳定检索】2024年人机交互与虚拟现实国际会议(HCIVR 2024)

会议简介 2024年人机交互与虚拟现实国际会议&#xff08;HCIVR 2024&#xff09;定于2024年11月15-17日在中国杭州召开&#xff0c;会议由浙江工业大学主办。人机交互&#xff0c;虚拟现实技术的发展趋势主要体现在系统将越来越实际化&#xff0c;也越来越贴近人类的感知和需求…