如何用C++扩展NodeJS的能力?_c++编写nodejs扩展,2024年最新2024大数据开发开发面试解答之设计模式篇

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

Google的V8引擎是用C++开发的,NodeJS本身大部分基础Addon也是C++开发的,因此C++和NodeJS的结合比想象中容易得很多:

node和V8都提供了完善的C++ API来管理JS原生的类型,对象和模块, 所有的JS对象,函数,都能在V8的API中找到相对应的类型,如果同时了解C++和JS的语法,可以极快上手。
无需定义专门的语言绑定层,例如Java的JNI。这是由于JS的所有函数在V8引擎中,实际上都是同样形式(同样的返回值和参数列表),因此无须JNI那种复杂的方法签名机制, 于是Node就帮我们包办了标准C的输出接口,开发者只要将精力更集中在实际的C++逻辑上就好了。
最重要的,Node改造了Google的GYP(Generate Your Project)作为NodeJS addon的夸平台编译工具node-gyp。GYP本身就是特点就是简单易用,而它基于JSON格式的配置文件,更是对Javascript开发者极度友好。

说了半天,可能有人要骂 No BB, show me the code,程序员不来点实际的怎么行。接下来,介绍一下NodeJS的C++需要准备哪些东西,如何使用node-gyp,以及,如何将一个C++的class做成一个JS的class,变得和js对象的实例那样变得有生命周期,自己的方法等。

通过Addon增强NodeJS

对计算密集任务可以用C++写NodeJS的Addon来提高性能的优势。然而究竟应该怎么做呢?先给出一个addon文件的例子:

-rwxrwxr-x 1 melode 88920 Jun 17 22:32 meshtool.node

可以看到NodeJS的addon就是一个后缀为node的文件,这货实际上应该就是一个动态链接库,通过JS代码

var tool = require('./build/Release/meshtool');

即可加载。然而怎么得到这个东东?待我细细道来。

环境的准备

1. node-gyp

这个是C++ addon的跨平台构建工具,有了它我们才能在各个平台编译出.node文件。可以通过一个简单的配置文件binding.gyp描述编译的内容,如源代码文件,依赖的头文件目录,静态库目录,编译器参数等。然后在主流平台上,它都可以将你的配置转化为一套构建脚本(Makefile或VS工程).

获取它很简单,可以通过npm安装:

npm install -g node-gyp

如果没有安装tnpm,用npm安装也可以,注意,在内网指定阿里内部的目录镜像可以爽到飞起:

npm install -g node-gyp --registry=http://registry.npm.aliyun.com

装完以后就可以使用 node-gyp这个命令了:)
第一次用以前,建议调用如下命令先:

node-gyp install --ensure --disturl=http://npm.taobao.org/dist

上面那个命令是安装node-gyp编译所需的node和v8等依赖库文件。若不运行也会在第一次编译时自动运行。但事先通过指定disturl安装的话,节约了等待时间。
你可以通过运行

node-gyp -v

来检查命令是否成功安装。

到此node-gyp已经安装完毕,可以用来编译项目的addon了。

首先得先准备好binding.gyp, 可以放在你项目的任何目录下,但要注意

  1. 要和你准备运行node-gyp命令的那个目录一致
  2. 配置中的涉及到的文件或目录的相对路径要从该目录开始。
    我们可以将它放在项目根目录。

配置文件的内容是一个大JSON,可以非常简单:

{
  "targets": [
    {
      "target\_name": "meshtool",
      "sources": [ “./meshfilereader.cpp”,"./index.cpp"]
     }]
}

以上配置指定了一个叫meshtool的编译目标, 该目标需要编译的cpp文件为 index.cpp和meshfilereader.cpp.

接着在项目放binding.gyp的目录运行 node-gyp configure, 该目录下会生成一个build的目录,内含构建所需的规则和makefile.
于是我们再运行node-gyp build, 在运行目录/build/Release下我们便可以找到所需的.node文件了。

2. nan (Native abstraction for NodeJS)

当你开始写Addon不久以后,你便会发现一个令人抓狂的现实:NodeJS 的 0.12.x,0.11.x和0.10.x因为使用的V8版本不同,存在严重的API不兼容的情况。不管你愿不愿意,你发现只能做到令其中一个版本通过编译。你开始苦恼C++不能像JS代码那样,不用改一行代码就同时满足所有NodeJS版本, 直到你发现了nan.

nan 是老外的一个项目(不是"Not A Number"),解决了不同NodeJS版本间API的不兼容问题,同样可以用npm安装:

npm install nan
//or
npm install -g nan

也可以直接配置到package.json中。

安装完后要在binding.gyp中配置好nan的头文件依赖,修改如下:

{
  "targets": [
    {
      "target\_name": "meshtool",
      "sources": [ “./meshfilereader.cpp”,"./index.cpp"],
      "include\_dirs" : [
           "<!(node -e \"require('nan')\")"
     }]
}

之所以include_dirs要这么写是因为这样可以自适应nan模块安装在全局和安装在本地的情况。如果确定安装方式的话,也可以直接写路径。

至此万事俱备,只欠代码。

编写Addon的C++代码

写代码前的预备知识
写Addon之前,建议先要了解一下Google V8的API,至少要了解以下的一些概念:

JS 基本类型 对应的V8原生C++ 原生类型 :

Javascript	V8
Number	v8::Number, v8::Integer
String	v8::String
Array	v8::Array
Object	v8::Object
Function	v8::Function

JS基本类型和V8的原生类型之间实际是等价的,也就是C++层从JS层获取到的JS基本对象和返回JS层的结果,都是以v8的上述的原生类型形式。

JS句柄 v8::Handle 它相当于一个智能指针,所有上面的C++的原生类型都是由Handle来引用的,相当于JS中的那个var 变量,因此不管是从JS层获取到的原生类型对象还是在C++内部构造出的原生类型对象,都是以 v8::Handle 形式给出来的。
v8::Handle分为两种,v8::Local和v8::Persistant, 前者只在当前Scope中有效,后者是代表全局变量。
v8::Local 的Scope由 HandleScope管理,由离最近的HandleScope分配,并随HandleScope生命周期结束而结束。而v8::Persistant的生命周期由自己的New和Dispose方法管理。
生命周期结束的Handle,其指向的对象会随时被垃圾收集回收。

JS方法的C++表示

必须为为全局函数或静态方法,根据V8版本不同固定为如下的形式:

V8 3.11

v8::Handle<v8::Value>  AnyMethodName(v8::Argument args)
V8 3.28

void AnyMethodName(v8::Argument args)

JS方法的传入参数 v8::Argument

不管什么样的JS函数,其C++方法的传入参数都是一个v8::Argument对象,这是因为v8::Arugment本身就是一个list, 内含可变数量的实际参数,如果想取第i个传入参数,只需要使用args[i] 即可。另外还可以通过args.This()获取this对象。

了解完概念,我们试着写一个输出一个方法的Addon
和写普通的JS模块一样,Addon的代码需要确定模块的输出,这里就是借助Nan写输出一个叫parseMesh的JS方法的Addon:

NAN\_METHOD(ParseMesh)
{
    NanScope();
    if(args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction())
    {
        return NanThrowError("Bad Arguments");
    }
    Handle<String> filename = args[0].As<String>();
    Handle<Function> callback = args[1].As<Function>();
    ...
    NanReturnUndefined();
}
 
void init(Handle<Object> exports)
{
    NODE\_SET\_METHOD(exports,"parseMesh",ParseMesh);
}
 
NODE\_MODULE(meshtool, init);

以上代码最后一行定义模块名称meshtool,和加载它的时候调用的初始化方法init.
而初始化方法中则设置了输出的函数名parseMesh, 而实际接受parseMesh调用的C++方法即ParseMesh。

再来看这个ParseMesh方法,由于前面所说,因为v8::Argument的存在,所有JS的函数在C++层的方法参数和返回值都是一致的,所以它可以被一个NAN_METHOD的宏来处理,该宏根据Node版本将方法展开成对应的形式。保证ParseMesh方法可以在初始化中注册为任意版本JS函数parseMesh的的实现。

最后的NanReturnUndefined()表示该方法返回undefined (没有返回值即返回undefined). Nan还有很多其他的Return形式可以使用。

到此我们已经可以用C++ Addon来输出简单的JS函数了,这对于大多数情况已经够用。然而NodeJS还提供了一些更高大上的东西,比如输出一个自定义的JS的类型,或者在C++中使用多线程,并异步执行回调等。

进阶

进阶1: 输出一个JS包装类型

前一篇只提到了如何输出一个JS方法,但有的时候如果我们想输出的是一个C++的对象呢,这种情况在想要包装一个现有的C++库到JS的时候出现的尤其频繁。
如果我们仅有输出C++方法成为JS函数一条路,那也有笨办法,用C++代码表示:

//C++
class Body
{
    Body();
    void Move();
};
 
NAN\_METHOD(CreateBody)
{
    NanScope();
    Body\* handle = new Body();
    NanReturnValue(NanNew<Integer>(reinterpret\_cast<int>(handle)));
}
 
NAN\_METHOD(BodyMove)
{
    //check arguments
      Body\* handle = reinterpret\_cast<Body\*>(args[0].As<Integer>()->intValue());
      handle->Move();
}
 
NAN\_METHOD(DestroyBody)
{
    //check arguments
     Body\* handle = reinterpret\_cast<Body\*>(args[0].As<Integer>()->intValue());
     delete handle;
}
void init(Handle<Object> exports) {
     NODE\_SET\_METHOD(exports,"createBody",CreateBody);
     NODE\_SET\_METHOD(exports,"bodyMove",BodyMove);
     NODE\_SET\_METHOD(exports,"destroyBody", DestroyBody);
}
NODE\_MODULE(native_body, init)

相应的使用native addon的JS代码:

var native=require("native\_body");
var handle = native.createBody();
native.bodyMove(handle);
native.bodyDestroy(handle);

其实就是将一个Body对象的指针作为JS的一个int变量让JS层持有,每当要操作该对象时,重新将该指针传回。
但是这样的实现有很多缺点:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!


其实就是将一个Body对象的指针作为JS的一个int变量让JS层持有,每当要操作该对象时,重新将该指针传回。  
 但是这样的实现有很多缺点:




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-Ah1p0y28-1713395438537)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值