您的位置:首页 > 健康 > 美食 > node.js c++拓展开发

node.js c++拓展开发

2024/10/6 5:53:45 来源:https://blog.csdn.net/m0_51560548/article/details/141893411  浏览:    关键词:node.js c++拓展开发

初始化项目

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++ 中的 NumberLocal<Number> arg2 = args[1].As<Number>();double num1 = arg1->Value();double num2 = arg2->Value();printf("num1:%lf num2:%lf\n", num1, num2); // 相当于 console.logLocal<Number> result = Number::New(args.GetIsolate(), num1 + num2); // 创建 Numberargs.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 函数的时候传入的resultLocal<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 函数的时候传入的 resultLocal<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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com