极客时间对于推广渠道会有返利优惠,比如山月在极客时间买了一门课,再把课程分享给好友购买,这时极客时间会向山月返利20元左右。
而我现在做了一个返利平台,你可以在上边通过山月的链接购买课程,此时极客时间会向我返利。为了共同学习,而你可以添加我的微信 (shanyue94),我将把极客时间给我的返利发一个红包全部返给你

# Node API 与 C++ 扩展

大家好,我是山月。

我还有工作的时候,据我同事说,他作为一个 Node 开发者去字节跳动面试时,面试官问到: 你写过 C++ 的扩展吗?

自此之后,他再也没有面试过 Node,前几天听闻他的消息时,他的 title 已变成前端开发工程师,当他再次大厂面试时,对以前的 Node 经验闭口不提。

今天将学习 node 中的 napi, 并从零写一个关于 C 的拓展。不啰嗦,直接开始。

  1. 如何使用 Node API 写一个 hello, world
  2. 如何使用 Node API 写一个 sum(a, b)
  3. 如何使用 Node API 迁移 C library

代码地址: shfshanyue/node-examples/napi (opens new window),可直接下载,并编译。

# 00 编译环境准备

  • python

# 01 node-gyp 配置

$ npm install -g node-gyp

$ npm init

在根目录添加文件 binding.gyp

{
  "targets": [{
    "target_name": "module",
    "sources": [ "./module.c" ]
  }]
}

# 02 第一个 C 扩展: hello, world

在项目根目录新建文件 module.c,文件内容如下

  • napi_env: 一个 v8 隔离实例的抽象
  • exports: 用以模块的导出
  • napi_value: JS 中变量的抽象
  • napi_set_named_property: 用以为 JS 对象创建属性
  • napi_create_string_utf8: 用以将 C 中的字符串转化为可供 JS 使用的 napi_value
// 关于 node api 的库
#include <node_api.h>

// 关于出自字符串的库,如以下获取字符串的长度
#include <string.h>

napi_value Init(napi_env env, napi_value exports) {
    napi_value nHello;
    char* hello = "hello, world";

    napi_create_string_utf8(env, hello, strlen(hello), &nHello);
    napi_set_named_property(env, exports, "hello", nHello);

    return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

可看出核心流程为:

  1. C 声明定义一个变量
  2. C 中的变量通过 API napi_create_xxx 转化为在 JS 中可以使用的 napi_value
  3. C 中通过 API napi_set_named_property 将该变量导出

# 03 编译

通过 node-gyp 可编译得到 build/Release/module.node,一个二进制文件。

.json.js 一样,.node 是 Node 中原生支持的模块,也是我们在代码中需要引用的路径。

$ node-gyp rebuild

$ tree
.
├── binding.gyp
├── build
│   ├── Makefile
│   ├── Release
│   │   ├── module.node
│   │   └── obj.target
│   │       └── module
│   │           └── module.o
│   ├── binding.Makefile
│   ├── config.gypi
│   ├── gyp-mac-tool
│   └── module.target.mk
├── index.js
├── module.c
└── package.json

4 directories, 11 files

# 04 在 Node API 中导出函数

一个 hello, world 过于简陋,这里示例一个高级版的 hello, world: 导出一个函数。

  • napi_value sum(napi_env env, napi_callback_info info): 创建一个可在 JS 环境中使用的函数
  • napi_get_cb_info(env, info, &argc, argv, NULL, NULL): 读取函数输入参数 (与读取脚本输入时 argc/argv 类似)

其余步骤与 hello, world 版相同,即在 C/JS 中变量进行切换。

#include <node_api.h>
#include <string.h>

napi_value sum(napi_env env, napi_callback_info info) {
    napi_status status;

    size_t argc = 2;
    napi_value argv[2];

    int a = 0;
    int b = 0;

    // 获取函数参数, argc 为参数的数量,argv 为参数数组
    napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
    
    // napi_value -> int: 类型转化
    napi_get_value_int32(env, argv[0], &a);
    napi_get_value_int32(env, argv[1], &b);
    
    int sum = a + b;

    napi_value nSum;
    // int -> napi_value: 类型转化
    status = napi_create_int32(env, sum, &nSum);
    return nSum;
}

napi_value Init(napi_env env, napi_value exports) {
    napi_value nSum;

    napi_create_function(env, NULL, 0, sum, NULL, &nSum);
    napi_set_named_property(env, exports, "sum", nSum);

    return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

# 05 移植已有的 C/C++ 库

在某种情况,我们无需自己开发 C/C++ 库,只需将成熟的 C/C++ 库进行移植。

int compute (int n)

此处,只需要补充以下胶水代码即可。

napi_value myGzip(napi_env env, napi_callback_info info) {
    napi_status status;

    size_t argc = 1;
    napi_value argv[1];

    int a = 0;

    // 获取函数参数, argc 为参数的数量,argv 为参数数组
    napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
    
    // napi_value -> int: 类型转化
    napi_get_value_int32(env, argv[0], &a);
    
    // 在此处进行胶水的粘合即可
    int c = compute(n)

    napi_value result;
    // int -> napi_value: 类型转化
    status = napi_create_int32(env, n, &result);
    return result;
}

# 06 版本发布

发包时,仅仅需要 module.node 即可,可大幅减小包的体积,也减轻使用该包的人的心智负担。

当发布之前,将 module.node 单独挪出,并将 build 目录清理。

{
  "build": "node-gyp rebuild && node-gyp clean",
  "prepublishOnly": "npm run build"
}

以下为 bingding.gyp 配置,成功编译后将目标模块单独摘出。

{
    "targets": [{
        "target_name": "module",
        "sources": ["./module.c"]
    }, {
        "target_name": "action_after_build",
        "type": "none",
        "copies": [{
            "files": ["<(PRODUCT_DIR)/module.node"],
            "destination": "./"
        }]
    }]
}

关于山月

我的项目:
我的微信:shanyue94,欢迎交流
Last Updated: 7/21/2019, 11:25:08 AM