最近的任务是把计划库的API用JavaScript语言调用起来,需要用Node.js的C++扩展,本文简单归总一下node.js addons官方文档https://nodejs.org/api/addons.html

1. 基本知识介绍

  在node.js中,除了用js写代码以外,还可以使用C++编写扩展,这有点类似DLL,动态链接进js代码中。使用上也相当方便,只需用require包含,这和一般的js模块并没有什么区别。C++扩展为js和C++代码的通信提供了一个接口。

  要编写node.js的C++扩展,需要了解一些基本知识:

1. V8: Google出品的大名鼎鼎的V8引擎,它实际上是一个C++类库,用来和 JavaScript 交互,比如创建对象,调用函数等等。V8的API大部分都声明在v8.h头文件中。
2. libuv:一个C实现的事件循环库,node.js使用libuv来实现自己的事件循环、工作线程和所有的异步行为。它是一个跨平台的,高度抽象的lib,提供了简单易用的、POSIX-like的方式来让操作系统和系统任务进行交互。比如和文件系统、sockets、定时器和系统事件。libuv还提供了POSIX threads线程级别的抽象来增强标准事件循环中不具备的复杂异步能力。我们鼓励C++扩展的作者思考如何通过转换I/O或其他耗时操作到非阻塞系统操作来避免阻塞事件循环。
3. node.js内部lib,node.js本身提供了很多C/C++ API来给扩展使用,比如最重要的一个:node::ObjectWrap类。
4. node.js包含了很多静态链接库,比如OpenSSL。这些库都放在node.js代码树的deps/目录下。只有V8和OpenSSL标识符被有意地被node.js重复导出来被各种扩展使用。

  下面快速地来看一个实例。

2. 第一个例子HelloWord

  下面的例子是一个简单的C++扩展,其功能相当于js的如下代码:

module.exports.hello = () => 'world';

  首先创建一个hello.cc:

// hello.cc
#include <node.h>namespace demo {using v8::FunctionCallbackInfo;using v8::Isolate;using v8::Local;using v8::Object;using v8::String;using v8::Value;void Method(const FunctionCallbackInfo<Value>& args) {Isolate* isolate = args.GetIsolate();args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));}void init(Local<Object> exports) {NODE_SET_METHOD(exports, "hello", Method);}NODE_MODULE(addon, init)
} // namespace demo 

  这个最简单的例子,已经出现了一些我们完全没有接触过的东西。大致解释一下:

1. 函数Method的参数类型是FunctionCallbackInfo<Value>&,FunctionCallbackInfo
2. Isolate,英文意思是“隔离”,在这里Isolate指的是一个独立的V8 runtime,可以理解为一个独立的V8执行环境,它包括了自己的堆管理器、GC等组件。后续的很多操作都要依赖于这个Isolate,后面我们会看到在很多操作中,都会使用Isolate的实例作为一个上下文传入。
(注:一个给定的Isolate在同一时间只能被一个线程访问,但如果有多个不同的Isolate,就可以给多个线程同时访问。不过,一个Isolate还不足以运行脚本,你还需要一个全局对象,一个执行上下文通过指定一个全局对象来定义一个完整的脚本执行环境。因此,可以有多个执行上下文存在于一个Isolate中,而且它们还可以简单安全地共享它们的全局对象。这是因为这个全局对象实际上属于Isolate,而却这个全局对象被Isolate的互斥锁保护着。)
3. 返回值需要用args.GetReturnValue().Set()来设置
4. 向外导出方法需要在扩展的初始化函数中使用NODE_SET_METHOD(exports, Method_Name, Method);。如果有多个方法需要导出,就写多个NODE_SET_METHOD

注意到node.js的C++扩展都必须按以下形式导出一个初始化函数(该函数名字可以随便设置一个):

void Initialize(Local<Object> exports);
NODE_MODULE(module_name, Initialize)

NODE_MODULE这行后面并没有分号(;),因为它并不是一个函数,你可以认为这是一个声明。module_name必须匹配最后生成的二进制文件的文件名(不包括.node后缀)。在hello.cc这个例子中,初始化函数是init,扩展模块名是addon。

构建(Building)

  写好源代码后我们就要把它编译成二进制的addon.node文件了。binding.gyp文件用来描述我们模块的构建配置,这个文件的内容是JSON形式的:

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

  实施构建操作需要用到node-gyp,如果尚未安装的话,需要运行(可能要用到sudo):

npm install -g node-gyp 

  来全局安装node-gyp。

  编写完binding.gyp文件,我们使用:

node-gyp configure

  来生成对应项目在当前平台的build目录。这将会在build目录下生成一个Makefile(Unix-like系统)或者一个vcxproj文件(Windows系统)还有一部分其他文件。接着,运行:

node-gyp build

  来生成一个编译过的addon.node文件,这个文件会被放在build/Release/目录下。

  build成功后,这个二进制的C++扩展就可以在node.js中使用require包含进来:

1 // hello.js
2 const addon = require('./build/Release/addon');
3 console.log(addon.hello()); // 'world'

  由于扩展的二进制文件的存放位置会根据编译方式不同而变化(有可能放在build/Debug/目录),所以可以用这种方式来引入扩展:

1 try {
2     return require('./build/Release/addon.node');
3 } catch (err) {
4     return require('./build/Debug/addon.node');
5 }

链接node.js依赖

  node.js使用一些静态链接库,比如V8、libuv和OpenSSL。所有扩展都必须链接V8,还有可能需要链接一些其他的库。典型情况下,使用#include <...>来include这些库(比如链接V8就是#include <v8.h>),node-gyp会自动找到这些库。然而,有几个注意事项需要说明:

1. node-gyp运行时,它会检测node.js的版本并且下载全部源码文件或者只是下载头文件。如果下载了全部源码文件,扩展就可以使用node.js的所有依赖,如果仅仅下载了头文件,则只有node.js导出的那些东西可以被使用。
2. node-gyp可以使用--nodedir选项来指定本地node.js映像,使用这个选项时,扩展可以使用全部的node.js依赖。

使用require加载C++扩展

  经过编译的node.js C++扩展的后缀名是.node(类似.so和.dll),require()函数会查找这些.node文件像初始化动态链接库那样初始化它们。

  当使用reqiure()时,.node后缀可以被省略。需要注意的是,node.js在使用reqiure()加载模块时,会优先加载js后缀的文件。比如说一个目录下有一个addon.js和一个addon.node,当使用require('addon')时,node.js会优先加载addon.js。

函数参数

  C++扩展可以暴露函数和对象出来让node.js访问。当从js中调用C++扩展中的函数时,实参和返回值必须映射到C/C++事先声明好的代码中。以下代码展示了C++扩展代码如何读取从js传递过来的函数实参和如何返回值:

// 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;// This is the implementation of the "add" method// Input arguments are passed using the// const FunctionCallbackInfo<Value>& args structvoid Add(const FunctionCallbackInfo < Value > &args) {Isolate * isolate = args.GetIsolate();// Check the number of arguments passed.if (args.Length() < 2) {// Throw an Error that is passed back to JavaScriptisolate - >ThrowException(Exception: :TypeError(String: :NewFromUtf8(isolate, "Wrong number of arguments")));return;}// Check the argument typesif (!args[0] - >IsNumber() || !args[1] - >IsNumber()) {isolate - >ThrowException(Exception: :TypeError(String: :NewFromUtf8(isolate, "Wrong arguments")));return;}// Perform the operationdouble value = args[0] - >NumberValue() + args[1] - >NumberValue();Local < Number > num = Number: :New(isolate, value);// Set the return value (using the passed in// FunctionCallbackInfo<Value>&)args.GetReturnValue().Set(num);}void Init(Local < Object > exports) {NODE_SET_METHOD(exports, "add", Add);}NODE_MODULE(addon, Init)
} // namespace demo

  编译成功后,这个扩展可以被node.js使用require()包含并使用:

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

回调函数

  一种很常见的做法是从js传递回调函数给C++调用,下面这个示例展示了如何做:

// addon.cc
#include < node.h >namespace demo {using v8: :Function;using v8: :FunctionCallbackInfo;using v8: :Isolate;using v8: :Local;using v8: :Null;using v8: :Object;using v8: :String;using v8: :Value;void RunCallback(const FunctionCallbackInfo < Value > &args) {Isolate * isolate = args.GetIsolate();Local < Function > cb = Local < Function > ::Cast(args[0]);const unsigned argc = 1;Local < Value > argv[argc] = {String: :NewFromUtf8(isolate, "hello world")};cb - >Call(Null(isolate), argc, argv);}void Init(Local < Object > exports, Local < Object > module) {NODE_SET_METHOD(module, "exports", RunCallback);}NODE_MODULE(addon, Init)} // namespace demo

 解释:

1. 传递回调函数,其实和传递普通参数没什么大的区别,使用

Local<Function> cb = Local<Function>::Cast(args[0]);

 可以获得这个回调函数。然后需要显式声明这个回调函数的参数个数和参数数组:

const unsigned argc = 1;
Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };

 调用这个回调函数需要传入isolate、参数个数argc、参数数组argv:

cb->Call(Null(isolate), argc, argv);

2. Init函数和之前有点不同,上面这个扩展的Init()使用了两个参数的形式(之前都是单参数),其中第二个参数接受一个module对象: 

void Init(Local<Object> exports, Local<Object> module) {NODE_SET_METHOD(module, "exports", RunCallback); // 相当于直接导出整个模块作为方法
}

这将允许扩展使用单个函数的形式代替之前往exports中添加函数作为属性的方式来完全地重写exports。因此可以直接用扩展的名字作为函数名来调用,这适用于此扩展只对外暴露一个方法的情况: 

1 // test.js
2 const addon = require('./build/Release/addon');
3 addon((msg) => {
4     console.log(msg); // 'hello world'
5 });

  

转载于:https://www.cnblogs.com/wjcoding/p/11039785.html

node.js的C++入门相关推荐

  1. Node.js 4.x 入门实战视频课程【讲师辅导】-曾亮-专题视频课程

    Node.js 4.x 入门实战视频课程[讲师辅导]-15843人已学习 课程介绍         [会员免费]链接 http://edu.csdn.net/lecturer/585 右侧办理会员卡. ...

  2. Node.js 6.x 入门-曾亮-专题视频课程

    Node.js 6.x 入门-2484人已学习 课程介绍         链接 http://edu.csdn.net/lecturer/585 右侧可办理会员卡.会员可免费看曾老师已出,和会员期内发 ...

  3. Node.js 官网入门教程(二) npm(安装、包版本、卸载、npx)、package.json(scripts、devDependencies)package-lock.json(语义版本规则符号

    Node.js 官网入门教程(二) npm(包管理.安装.包版本.卸载.npx).package.json(scripts.devDependencies).package-lock.json(语义版 ...

  4. Node.js前端工程师入门速成-姜威-专题视频课程

    Node.js前端工程师入门速成-11129人已学习 课程介绍         随着"大前端战略"的技术革新,前端工程师的项目领域也变得异常宽广,从客户体验的设计层面,延伸到流畅性 ...

  5. Node.js 官网入门教程(一) CommonJS 模块规范、Node.js REPL、console、CLI、exports

    Node.js 官网入门教程(一) CommonJS 模块规范.Node.js REPL.console.CLI.exports 文章目录 Node.js 官网入门教程(一) CommonJS 模块规 ...

  6. Node.js 指南(入门指南)

    入门指南 安装Node之后,让我们尝试构建我们的第一个Web服务器,创建名为"app.js"的文件,并粘贴以下代码: const http = require('http');co ...

  7. Node.js 小白入门课3-设置基本数据库

    Node.js 小白入门课3-设置基本数据库 Sequelize(框架,数据库包文件,简化数据库编写)是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL ...

  8. Apache Ignite的Node.js客户端使用入门

    为什么80%的码农都做不了架构师?>>>    介绍 Ignite原生提供了若干种主要编程语言的支持,最近,还通过瘦客户端技术对其它的编程语言提供了支持,其中在2.7版本中新增加的瘦 ...

  9. node.js Express框架入门

    一.为什么要用Express框架? Node.js由于不需要另外的HTTP服务器,因此减少了一层抽象,给性能带了不少的提升,同时也因此提高了开发难度,实现一个POST数据的表单,例如: <for ...

  10. Node.js 极简入门Helloworld版服务器例子

    粗浅得很,纯属备忘. // 内置http模块,提供了http服务器和客户端功能(path模块也是内置模块,而mime是附加模块) var http=require("http"); ...

最新文章

  1. bluecam连接步骤说明_磊科路由器登录设置及默认密码说明
  2. 国家地理:子宫日记 Womb
  3. 阎崇年:《袁崇焕传》自序
  4. HTTP 错误 500.21 - Internal Server Error
  5. 2.HTML基本格式
  6. PAT甲级1131 Subway Map (30分):[C++题解]堆优化dijkstra、单源最短路、地铁地图、巧妙地建图套dijkstra模板!!
  7. nowcoder 河 我 车 题 错 天 乐 赛 倍增处理
  8. Linux查看文件内容命令:cat, tail, head, more, less
  9. linux 嵌入式 人工智能,嵌入式人工智能有哪些相关技术
  10. a:10 GET http://localhost:8080/static/js/xxx.js net::ERR_ABORTED 404
  11. fastText、TextCNN、TextRNN……这里有一套NLP文本分类深度学习方法库供你选择
  12. 《Spring揭秘》
  13. 进销存库存管理软件哪个好用
  14. HTML Web教程
  15. win10添加uefi linux引导,win10 ubuntu双系统:UEFI GPT和Legacy MBR引导模式
  16. ARP表 MAC表 路由表
  17. 《早起的奇迹》(死过一次的人生赢家)
  18. ROS中机械手臂的运动规划
  19. 简述人工智能发展的先决条件
  20. 佐治亚理工计算机科学,佐治亚理工学院计算机科学专业如何?

热门文章

  1. 【笔记】Yale博弈论第一课
  2. 利用自定义事件实现不同窗体间的通讯 -- C#篇
  3. Javascript 日期校验完备全过程
  4. 回文数问题,两数互换位置问题(不用第三个变量)
  5. Velocity模板基本常用语法
  6. 生成html数据字典,PHP生成html格式数据字典
  7. 【渝粤教育】国家开放大学2018年秋季 0242-21T机械制图 参考试题
  8. 【渝粤教育】国家开放大学2018年秋季 7048-21T危急重症护理学(本) 参考试题
  9. 浅谈协方差矩阵 再谈协方差矩阵之主成分分析
  10. MATLAB 与Modelsim之间对测试系统的联合仿真