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

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

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

我们从Hello World开始:

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

  1. {
  2. "name": "test-cpp-module",
  3. "version": "0.1.0",
  4. "private": true,
  5. "gypfile": true
  6. }

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

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

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

  1. {
  2. "targets": [
  3. {
  4. "target_name": "democpp",
  5. "sources": [
  6. "democpp.cc"
  7. ],
  8. "include_dirs": [
  9. "<!@(node -p \"require('node-addon-api').include\")"
  10. ],
  11. "dependencies": [
  12. "<!(node -p \"require('node-addon-api').gyp\")"
  13. ],
  14. "cflags!": ["-fno-exceptions"],
  15. "cflags_cc!": ["-fno-exceptions"],
  16. "defines": ["NAPI_CPP_EXCEPTIONS"],
  17. "xcode_settings": {
  18. "GCC_ENABLE_CPP_EXCEPTIONS": "YES"
  19. }
  20. }
  21. ]
  22. }
  • 文件中首先使用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文件:
  1. #include <napi.h>
  2. using namespace Napi;
  3. String Hello(const CallbackInfo& info) {
  4. return String::New(info.Env(), "world");
  5. }
  6. Napi::Object Init(Env env, Object exports) {
  7. exports.Set("hello", Function::New(env, Hello));
  8. return exports;
  9. }
  10. 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,顺利的话会得到这样的输出信息:

  1. $ npm install
  2. > test-cpp-module@0.1.0 install /home/andrew/Documents/dev/html/nodejs/callcpp
  3. > node-gyp rebuild
  4. SOLINK_MODULE(target) Release/nothing.node
  5. CXX(target) Release/obj.target/democpp/democpp.o
  6. SOLINK_MODULE(target) Release/democpp.node

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

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

  1. > $ node
  2. > democpp=require("./build/Release/democpp.node")
  3. { hello: [Function] }
  4. > democpp.hello()
  5. 'world'
  6. >

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

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

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

  1. {
  2. "targets": [
  3. {
  4. "target_name": "democpp",
  5. "sources": [
  6. "democpp.cc"
  7. ],
  8. "include_dirs": [
  9. "<!@(node -p \"require('node-addon-api').include\")"
  10. ],
  11. "libraries": [
  12. '-lssl -lcrypto',
  13. ],
  14. "dependencies": [
  15. "<!(node -p \"require('node-addon-api').gyp\")"
  16. ],
  17. "cflags!": ["-fno-exceptions"],
  18. "cflags_cc!": ["-fno-exceptions"],
  19. "defines": ["NAPI_CPP_EXCEPTIONS"],
  20. "xcode_settings": {
  21. "GCC_ENABLE_CPP_EXCEPTIONS": "YES"
  22. }
  23. }
  24. ]
  25. }

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

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

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

  1. #include <napi.h>
  2. #include <openssl/md5.h>
  3. using namespace Napi;
  4. void openssl_md5(const char *data, int size, unsigned char *buf){
  5. MD5_CTX c;
  6. MD5_Init(&c);
  7. MD5_Update(&c,data,size);
  8. MD5_Final(buf,&c);
  9. }
  10. String GetMD5(const CallbackInfo& info) {
  11. Env env = info.Env();
  12. std::string password = info[0].As<String>().Utf8Value();
  13. //printf("md5 in str:%s %ld\n",password.c_str(),password.size());
  14. unsigned char hash[16];
  15. memset(hash,0,16);
  16. openssl_md5(password.c_str(),password.size(),hash);
  17. char tmp[3];
  18. char md5str[33]={};
  19. int i;
  20. for (i = 0; i < 16; i++){
  21. sprintf(tmp,"%02x",hash[i]);
  22. strcat(md5str,tmp);
  23. }
  24. return String::New(env, md5str,32);
  25. }
  26. String Hello(const CallbackInfo& info) {
  27. return String::New(info.Env(), "world");
  28. }
  29. Napi::Object Init(Env env, Object exports) {
  30. exports.Set("hello", Function::New(env, Hello));
  31. exports.Set("md5", Function::New(env, GetMD5));
  32. return exports;
  33. }
  34. NODE_API_MODULE(addon, Init)

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

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

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

  1. > $ node
  2. > democpp=require("./build/Release/democpp.node")
  3. { hello: [Function], md5: [Function] }
  4. > democpp.hello()
  5. 'world'
  6. > democpp.md5("abc")
  7. '900150983cd24fb0d6963f7d28e17f72'
  8. >

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

  1. $ echo -n "abc" | openssl md5
  2. 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. CF987B - High School: Become Human

    Year 2118. Androids are in mass production for decades now, and they do all the work for humans. But ...

  2. idea中自动生成实体类

    找到生成实体的路径,找到Database数据表 找到指定的路径即可自动生成entity实体 在创建好的实体类内如此修改 之后的步骤都在脑子里  写给自己看的东西 哪里不会就记录哪里 test类(以前都 ...

  3. 派多个订单给一个司机,拒单是同一订单id

    问题:多次派单给一个司机,发现多个拒单请求是同一个订单id的. 原因:来单页面是SingleTask, 并且没有重写onNewIntent, 而倒计时结束拒单的时候会弹窗提示,只有点了确认按钮才会把当 ...

  4. HCNA(华为)_DHCP篇

    在大型的企业网络中,会有大量的主机或设备需要获取IP地址等网络参数.如果采用手工配置,工作量大 且不好管理,如果有用户擅自修改网络参数,还有可能会造成IP地址冲突等问题.使用动态主机配置协DHCP 来 ...

  5. TCP 三次握手、四次挥手

    三次握手:(主要是server.client相互同步系列号) SYN:同步序列号 ACK:确认序列号 第一次握手:client 向server 发送SYN,seq=x,申请同步client端序列号,c ...

  6. JDBC连接Oracle错误ORA-00922: 选项缺失或无效

    以下错误: ORA-00922: 选项缺失或无效 ORA-00922: missing or invalid option 是由于: execute(sql)语句多了个分号 ; 你没看错!!! 在sq ...

  7. Nuxt.js 从入门到放弃

    Nuxt 是 Vue 上的 SSR,也就是服务端渲染应用框架,可在很大程度上解决当前 SPA 和 CSR 的首页加载慢,不利于 SEO 的问题.本场 Chat 就将详细介绍 Nuxt 的使用以及相关概 ...

  8. CMD 中常见命令

    引自百度经验:https://jingyan.baidu.com/article/67508eb41d44a09cca1ce4f1.html ipConfig:查询ip ping:查询连接速度: pi ...

  9. Python(day1)

    一.Python的属于解释型语言. 编译型:一次性,将全部的程序编译成二进制文件,然后再运行. 优点:运行速度快. 缺点:开发效率低,不能跨平台. 解释型:当你的程序运行时,一行一行的解释,并运行. ...

  10. SpringAop注解实现日志的存储

    一.介绍 1.AOP的作用 在OOP中,正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增加.AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封 ...