因为项目的原因,最近经常使用node.js搭RESTful接口。

性能还是很不错啦,感觉比Spring Boot之类的要快。而且在不错的性能之外,只要程序结构组织好,别让太多的回调把程序结构搞乱,整体开发效率比Java快的就太多了。

如果想进一步提高效率,使用c++来优化部分模块是不错的选择。尤其可贵的是nodejs对于同c++的混合编程支持的很好,个人感觉跟写Python的扩展模块处于同样的易用水平。

我们从Hello World开始:

首先要有一个空白的工作目录,在其中建立一个node包管理文件package.json,内容为:

{
"name": "test-cpp-module",
"version": "0.1.0",
"private": true,
"gypfile": true
}

随后在目录中执行命令:npm install node-addon-api --save安装nodejs扩展模块的开发支持包。这里假设你已经安装配置好了nodejs和相应的npm包管理工具,还有xcode的相关命令行编译工具。我们不重复这些基本工具的安装配置,需要的话请参考官网相关文档。

上面命令执行完成,我们就完成了基本开发环境的配置。

c++的模块由binding.gyp文件描述,并完成自动编译的相关配置工作,我们新建一个binding.gyp文件,内容为:

{
"targets": [
{
"target_name": "democpp",
"sources": [
"democpp.cc"
],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
"dependencies": [
"<!(node -p \"require('node-addon-api').gyp\")"
],
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"defines": ["NAPI_CPP_EXCEPTIONS"],
"xcode_settings": {
"GCC_ENABLE_CPP_EXCEPTIONS": "YES"
}
}
]
}
  • 文件中首先使用target_name指定了编译之后模块的名称。
  • sources指明c++的源文件,如果有多个文件,需要用逗号隔开,放到同一个数组中。
  • include_dirs是编译时使用的头文件引入路径,这里使用node -p执行node-addon-api模块中的预置变量。
  • dependencies是必须的,不要改变。
  • 后面部分,cflags!/cflags_cc!/defines三行指定如果c++程序碰到意外错误的时候,由NAPI接口来处理,而不是通常的由c++程序自己处理。这防止因为c++部分程序碰到意外直接就退出了程序,而是由nodejs程序来捕获处理。如果是在Linux中编译使用,有这三行就够了。
  • 但如果是在macOS上编译使用,则还要需要最后一项xcode-settings设置,意思相同,就是关闭macOS编译器的意外处理功能。

    最后是c++的源码,democpp.cc文件:
#include <napi.h>

using namespace Napi;

String Hello(const CallbackInfo& info) {
return String::New(info.Env(), "world");
}
Napi::Object Init(Env env, Object exports) {
exports.Set("hello", Function::New(env, Hello));
return exports;
}
NODE_API_MODULE(addon, Init)

程序中引入napi.h头文件,使用Napi的namespace还有最后的NODE_API_MODULE(addon,Init)都是模板化的,照抄过来不用动。

Init函数中,使用exports.Set()引出要暴露给nodejs调用的函数。如果有多个需要引出的函数,就写多行。

Hello函数是我们主要完成工作的部分,本例中很简单,只是用字符串的方式返回一个“world”。

以上democpp.cc/binding.gyp/package.json三个文件准备好之后,在命令行执行:npm install,顺利的话会得到这样的输出信息:

$ npm install

> test-cpp-module@0.1.0 install /home/andrew/Documents/dev/html/nodejs/callcpp
> node-gyp rebuild SOLINK_MODULE(target) Release/nothing.node
CXX(target) Release/obj.target/democpp/democpp.o
SOLINK_MODULE(target) Release/democpp.node

这表示编译顺利完成了,如果碰到错误,可以根据错误信息去判断解决方案。通常都是环境配置缺少相关程序或者上述的三个文件有打字错误。

下面我们验证一下模块的编译结果,在命令行使用nodejs,引入编译的模块文件,然后调用hello函数来看看:

> $ node
> democpp=require("./build/Release/democpp.node")
{ hello: [Function] }
> democpp.hello()
'world'
>

上面是最简单的一个范例,下面我们增加一点难度。在GNU的环境下,通常我们的程序都会包含很多第三方的扩展库,我们这里再举一个调用openssl的例子:

package.json文件不用修改,我们不需要在nodejs层面增加新的依赖包。

编译带第三方扩展库的c++程序,通常需要在编译时指定额外的头文件包含路径和链接第三方库,这些都是在binding.gyp中指定的,这些指定在nodejs自动编译的时候,会解析并应用在命令行的编译工具中。

{
"targets": [
{
"target_name": "democpp",
"sources": [
"democpp.cc"
],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
"libraries": [
'-lssl -lcrypto',
],
"dependencies": [
"<!(node -p \"require('node-addon-api').gyp\")"
],
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"defines": ["NAPI_CPP_EXCEPTIONS"],
"xcode_settings": {
"GCC_ENABLE_CPP_EXCEPTIONS": "YES"
}
}
]
}

在macOS和常用linux版本中,openssl的头文件会自动安装在系统的头文件路径中,比如/usr/local/include,所以这里头文件的引入路径并没有增加。如果使用了自己安装的扩展库,需要在include_dirs一节增加新的头文件包含路径。

接着增加了libraries一节,指定了Openssl扩展库的链接参数-lssl -lcrypto,这个是必须的。

最后是修改democpp.cc文件,添加一个使用openssl中的md5算法对字符串进行md5编码的函数:

#include <napi.h>
#include <openssl/md5.h> using namespace Napi; void openssl_md5(const char *data, int size, unsigned char *buf){
MD5_CTX c;
MD5_Init(&c);
MD5_Update(&c,data,size);
MD5_Final(buf,&c);
} String GetMD5(const CallbackInfo& info) {
Env env = info.Env();
std::string password = info[0].As<String>().Utf8Value();
//printf("md5 in str:%s %ld\n",password.c_str(),password.size());
unsigned char hash[16];
memset(hash,0,16);
openssl_md5(password.c_str(),password.size(),hash);
char tmp[3];
char md5str[33]={};
int i;
for (i = 0; i < 16; i++){
sprintf(tmp,"%02x",hash[i]);
strcat(md5str,tmp);
}
return String::New(env, md5str,32);
} String Hello(const CallbackInfo& info) {
return String::New(info.Env(), "world");
}
Napi::Object Init(Env env, Object exports) {
exports.Set("hello", Function::New(env, Hello));
exports.Set("md5", Function::New(env, GetMD5));
return exports;
}
NODE_API_MODULE(addon, Init)

为了工作方便,源码中增加了一个没有引出的openssl_md5函数,仅供程序内部使用。因为没有引出,nodejs并不知道这个函数的存在。

从nodejs传递参数给c++的函数,是使用info[0].As<String>().Utf8Value()这样的形式。返回值到nodejs在hello函数中就已经看过了。

各项修改完成,同样回到命令行使用npm install重新编译。编译的过程和信息略,我们直接看调用的测试:

> $ node
> democpp=require("./build/Release/democpp.node")
{ hello: [Function], md5: [Function] }
> democpp.hello()
'world'
> democpp.md5("abc")
'900150983cd24fb0d6963f7d28e17f72'
>

想验证一下计算的正确性?可以直接执行openssl试试:

$ echo -n "abc" | openssl md5
900150983cd24fb0d6963f7d28e17f72

嗯,无悬念的相同。

参考文档

https://github.com/kriasoft/nodejs-api-starter

https://github.com/nodejs/node-addon-api/blob/master/doc/node-gyp.md

简单上手nodejs调用c++(c++和js的混合编程)的更多相关文章

  1. node.js基础 1之简单的nodejs模块

    模块流程: 创建模块->导出模块->加载模块->使用模块 ndoejs主要就是把项目变成模块化在管理 实现一个模块的调用,编写student.js.teacher.js.klass. ...

  2. 实现简单的PHP接口,以及使用js/jquery ajax技术调用此接口

    主要介绍下如何编写简单的php接口,以及使用js/jquery的ajax技术调用此接口. Php接口文件(check.php): <?php $jsonp_supporter = $_GET[‘ ...

  3. 【技术文章】《快速上手nodejs》

    本文地址:http://www.cnblogs.com/aiweixiao/p/8294814.html 原文地址: 扫码关注微信公众号 1.写在前面   nodejs快速上手   nodejs使ja ...

  4. 一个简单的nodejs项目(cat-names)分析

    https://github.com/sindresorhus/cat-names 一个非常简单的nodejs项目,用来方便的获取猫猫的名字: 安装: npm install --save cat-n ...

  5. nodejs调用jar

    目前nodejs调用jar主要有两种方式: 通过创建子进程运行java -jar命令调用包含main方法的jar 使用node-java通过c++桥接调用jar 方法一(子进程运行): const { ...

  6. 学习NodeJS第一天:node.js引言

    Node.JS 是资深 C 程序猿 Ryan Dahl(http://four.livejournal.com/)的作品,根据 Google 著名的开源 JavaScript 引擎 V8 来进行二次开 ...

  7. jQuery简单的Ajax调用示例

    jQuery确实方便,下面做个简单的Ajax调用: 建立一个简单的html文件: <!DOCTYPE HTML> <html> <head> <script ...

  8. 学习NodeJS第一天:node.js介绍

    Node.JS 前辈 C 程序猿 Ryan Dahl(http://four.livejournal.com/)工程,根据 Google 著名的开源 JavaScript 发动机 V8 对于二次开发 ...

  9. iOS js oc相互调用(JavaScriptCore)---js调用iOS --js里面通过对象调用方法

    下来我们看第二种情况 就是js 中是通过一个对象来调用方法的. 此处稍微复杂一点我们需要使用到 JSExport 凡事添加了JSExport协议的协议,所规定的方法,变量等 就会对js开放,我们可以通 ...

随机推荐

  1. Nginx upstream的5种权重分配方式分享

    Nginx负载均衡的分发方式有4种: 1.轮询,默认采取此方式,Nginx会按照请求时间的先后顺序进行轮询分发,若某台Web Server宕机,Nginx自动将其摘掉. 2.weight,权重,即轮询 ...

  2. Python爬虫代理IP池

    目录[-] 1.问题 2.代理池设计 3.代码模块 4.安装 5.使用 6.最后 在公司做分布式深网爬虫,搭建了一套稳定的代理池服务,为上千个爬虫提供有效的代理,保证各个爬虫拿到的都是对应网站有效的代 ...

  3. python2用pip进行安装时报错Fatalerrorinlauncher:Unabletocreateprocessusing"

    win10下python3和python2共存环境 用pip安装一个包执行pip2 install xxx的时候报错Fatal error in launcher: Unable to create ...

  4. Adams 2013自定义插件方法zz

    1.Adams插件介绍 Adams的高级模块(如Controls控制模块.Vibration振动模块.Durability耐久性模块等)是以插件的形式集成在Adams软件中.通过Adams提供的插件管 ...

  5. AppImage格式安装包使用

    AppImage(以及前身klik和portablelinuxapps)不会安装传统意义上的软件(即它不会将文件放在系统中的所有位置). 它每个应用程序使用一个文件.每个都是自包含的:它包括应用程序所 ...

  6. 近期待学习&目标内容

    算法 Splay 树链剖分 AC自动机 问题 bzoj1010[HNOI2008]玩具装箱 bzoj1096[ZJOI2007]仓库建设 bzoj1597[USACP2008 Mar]土地购买 bzo ...

  7. LoadRunner(四)——深度了解LR相关功能

    参考学习感谢:<精通软件性能测试与LoadRunner实战> 相关功能: 1 无工具情况下的性能测试 2性能测试工具LoadRunner的工作原理 3 VuGen应用介绍 4 协议的类型及 ...

  8. LoadRunner(三)——LR相关概念&组成部分

    参考学习感谢:<精通软件性能测试与LoadRunner实战> 一.运行机制和主要组成部分 1.LoadRunner主要由VuGen.Controller和Analysis三部分构成: 2. ...

  9. python从入门到实践-6章字典

    #!/user/bin/env python# -*- coding:utf-8 -*- # 前面不用空格,后面空格# 访问只能通过keyalien_0 = {'color': 'green', 'p ...

  10. ThinkPHP5 打开多语言支持

    1.在thinkphp\start.php 页面中添加多语言的切换函数的参数格式,本贴目前只支持两种语言,并注意必须全部小写!全部小写!!全部小写!!!,注意这一步很关键 Lang::setAllow ...