编写原生 Node.js 模块

本文作者:ivweb 程柳锋

当Javascript的性能遇到瓶颈,或者需要增强Javascript能力的时候,就需要依赖native模块来实现了。

应用场景

日常工作中,我们经常需要将原生的Node.js模块做为依赖并在项目中进行使用。下面有个列表,你可能对它们的名字很熟悉:

通常,我们开发原生Node.js模块包括但不仅限于以下原因:

  • 对性能有比较苛刻要求的应用。尽管Node.js得益于libuv,在异步I/O操作很有优势,但遇到数字计算时并不是一个很好的选择。
  • 使用更加底层的API,比如操作系统层面的。
  • 在C/C++和Node.js之间创建一个Bridge,进行通信。

什么是原生模块?

Node.js Addons是动态链接的可共享对象,由C/C++编写而成。可以在Node.js中通过require()方法进行调用,使用起来像调用Node.js普通模块一样。 —— 来自Node.js官方文档

这意味着如果处理得当的话,模块调用者使用由C/C++编写的原生模块的方式和由Node.js编写的模块一样。想要编写Node.js addons,你需要了解一些基本知识:

推荐阅读这些资料。

创建Node.js的原生扩展模块

下面我以一个常见的动态规划问题-青蛙跳台阶为例子来说明如何创建一个原生的Node.js模块。青蛙跳台阶描述为:一只青蛙一次可以跳上一级台阶,也可以跳上2级台阶,求该青蛙跳上n级台阶的共有多少种跳法?

首先创建一个frog_jump.cc原生文件,.cc的意思是c with class,扩展名也可以是.cpp。Google Style Guide建议使用.cc,那么此处还是以.cc做为扩展名吧。代码如下:

 #include <node.h>
#include<vector>

/**
 * Native method, calculate all ways frog jump to a target stair.
 */
int climbStairs(int n) {
  std::vector<int> dp(n);

  dp[1] = 1;
  dp[2] = 2;

  for (int i = 3; i <= n; i ++ ) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
}

/**
 * Export native method jumpTo
 */
void JumpTo(const v8::FunctionCallbackInfo<v8::Value>& args) {
  v8::Isolate* isolate = args.GetIsolate();

  // Check input type
  if (!args[0] -> IsNumber()) {
    isolate -> ThrowException(v8::Exception::TypeError(
      v8::String::NewFromUtf8(isolate, "Wrong arguments type!")));
  }

  int value = climbStairs(args[0] -> NumberValue());
  v8::Local<v8::Number> num = v8::Number::New(isolate, value);

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

// init is entry point.
void init(v8::Local<v8::Object> exports) {
  NODE_SET_METHOD(exports, "jumpTo", JumpTo);
}

NODE_MODULE(frog_jump, init)
 

对这段代码的解释:

  • #include 是c++里面引入头文件的方式,具体源码:node.h,C++链接时会加载这个头文件。头文件里面引入了v8命名空间,我们可以通过v8::标志来访问v8的接口。访问所有v8的类型,都需要使用v8::标志
  • 通过args对象来访问Node.js传递过来的参数,通过args也可以获取调用相关信息。
  • 通过v8::Isolate*可以获取函数作用域,可以像JS里面一样进行变量赋值,而不用担心垃圾回收问题,垃圾回收器会自动进行。
  • args.GetReturnValue()可以对函数返回的结果进行设置。
  • 任何原生Node.js模块都需要调用NODE_MODULE,NODE_MODULE是一个宏,它会进行模块注册操作。
  • C++ 有丰富的内置类型来保存数字或者字符串,但是JS只能识别v8::里面定义的类型。因此,将c++的变量赋值给JS时,需要转换成可以被JS识别的类型,也即是v8::定义的类型。比如v8::String、v8::Object。

编译原生的Node.js模块

一旦源代码编写完成,需要将它编译成二进制的addon.node文件,之后才能被Node.js require。为了完成编译操作,需要在项目的根目录创建binding.gyp文件,里面定义了Build的配置。binding.gyp的内容是一个JSON。

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

编译环境配置:

  • windows: 以管理员的身份运行npm install --global --production windows-build-tools,这个会安装所有编译依赖的工具。
  • linux: 安装python v2.7、make和GCC
  • osx: 安装xcode

虽然npm内置了一个node-gyp版本,但是该版本没有开放给开发者进行调用。npm install的时候会调用它来进行编译和安装工作。因此,开发者想要调用node-gyp必须自己安装一个全局的node-gyp版本。

$ npm install node-gyp -g
$ node-gyp configure
$ node-gyp build

运行node-gyp configure命令会生成一个跨平台的build文件,unix环境会生成Makefile,windows环境会在build目录里面生成vcxproj。

运行node-gyp build命令会生成可被Node.js调动的addon.node二进制文件。

Node.js中调用原生模块

 const frogJump = require('./build/Release/frog_jump');

frogJump.jumpTo(20);  //青蛙跳到第20个台阶的所有方法

项目源代码:frog-jump

后续

nan,即Native Abstractions for Node.js。它基于Node.js API接口,兼容所有Node版本,目前的最佳实践是基于nan来扩展原生模块,而不是直接使用Node.js API。

N-API,Node官方推出的用来编写原生Node扩展模块,是V8和nan的替代,目前处于实验阶段。

原文链接:http://www.ivweb.io/topic/592ec06609439b0640aefbc2

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏happyJared

Jmeter 压测 http(s)

  上一篇文章关于Jmeter介绍了Jmeter入门相关的知识。本文是实战篇,讲讲如何使用Jmeter对Http(s)进行压力测试。

782
来自专栏小樱的经验随笔

【批处理学习笔记】第七课:简单的批处理命令(6)

set 命令   set     设置环境变量 [适用场合]  设置声卡和路径等时用到 [用  法]  set [环境变量]=[字符串]        ...

3045
来自专栏数据魔术师

数据结构-线性表|顺序表|链表(下)

1 1 1 哈喽。各位小伙伴好久不见,热心的小编赶在开学季又来给大家送上满满的干货了。祝大家开心快乐! 继上两次咱们聊了顺序表、单链表、静态链表等知识。那么热爱...

2577
来自专栏炉边夜话

你的变量究竟存储在什么地方?

我相信大家都有过这样的经历,在面试过程中,考官通常会给你一道题目,然后问你某个变量存储在什么地方,在内存中是如何存储的等等一系列问题。不仅仅是在面试中,学校里...

1311
来自专栏流柯技术学院

Jmeter对基于websocket协议的压力测试

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。

2973
来自专栏开发技术

spring-boot-2.0.3不一样系列之源码篇 - run方法(二)之prepareEnvironment,绝对有值得你看的地方

  此系列是针对springboot的启动,旨在于和大家一起来看看springboot启动的过程中到底做了一些什么事。如果大家对springboot的源码有所研...

843
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-day01-基础题

1. 简述java语言,具有哪些特性? (1).java语言是简单的 java语言是和c++语言类似的,其次java中丢弃了c++中一些难理解的特性,比如运算符...

2564
来自专栏瓜大三哥

yaffs_bitmap

1.static inline u8 *yaffs_block_bits(struct yaffs_dev *dev, int blk)//计算给定块的字节数 ...

1725
来自专栏游戏杂谈

Aptana与Editplus中查找并替换的正则表达式应用

复杂、多人协作的时候会使用Aptana,简单、单个作战的时候通常会选用Editplus,而在开发过程中或多或少需要用到正则表达式去替换一些字符串,掌握了方法,对...

693
来自专栏FreeBuf

技术讨论 | Windows 10进程镂空技术(木马免杀)

在Win10 x64环境下替换正常的进程,是一个比较高超的技术。使用该技术,可以内存执行病毒、木马。在文件层面实现免杀。可以把一个木马使用DES加密,放在资源里...

951

扫码关注云+社区