NodeJs C/C++ 扩展入门

1、注:此教程默认你具备一定的 C/C++ 基础语法知识
2、如:指针、结构体等
3、本教程以 windows 操作系统为例,假设你会简单的使用 Visual Studio

系列文章

  1. C/C++ Addons 入门 Hello world!
  2. C/C++ Addons 对象参数及回调函数
  3. C/C++ Addons 非阻塞多线程回调
  4. C/C++ Addons windows 下 .dll 动态链接库 实用篇

完整代码

前置准备

工具
  • node-gyp
    • Google 出品的跨平台构建工具,初衷是用来打包 chromium 的
    • gypgenerate your package,将你的 C/++ 代码编译成 node.js 可识别的文件
    • 类似 webpackvue、jsx 等方言编译成为浏览器可识别文件
    • 也可以用 cMake.js 做同样的事情
    • .node 文件在 windows 平台下既 .dll 在 *nix 平台下 .so 文件,.node 的尾缀只是看起来 自然些 (20.0404 - 深入浅出node.js)
  • python
  • Visual Studio2017
    • C/C++ 在 windows 下依赖 VS - 个人推荐
    • 官方给出了木有 VS 的方案,windows-build-tools 但这没法用最重要语法提示、报错功能 - 不推荐
姿势

目前一共有三种方式可以编写 node.js 扩展,本文以官方推荐的 N-API 为例

  • N-API
    • node.js 由官方维护的 node.js 扩展 api
    • 纯 C 语法不依赖 node.js 版本,node.js 更新后基于 N-API 写的插件照样用,官方的解释是底层调用的 node.js 稳定版的二进制接口
  • node-addon-api
    • N-API 的 C++ 包装版本(有对象,更美好😝),目前 (Release 2.0.0) 并未完全的包装 N-API 的所有 api
  • nan
    • N-API 没出来之前主要的插件开发方式
    • “虽然”依赖 node.js 版本,但是维护团队很卖力,帮忙做好了每个版本的编译所以就 不依赖 node.js 版本了 👍
  • 原生 C/C++
    • 极度复杂,需要用一些 v8 api、源码
    • 依赖 node.js 版本,所以很难用 👎

起步

  1. 安装依赖
$ yarn add -D node-gyp # 就这一个依赖就够了
  • 个人很喜欢安装到项目里面,而不是 yarn add -g node-gyp
  • package.js 配置 scripts
{
  "scripts": {
    "configure": "node-gyp configure",
    "build": "node-gyp build",
    "clean": "node-gyp clean",
    "rebuild": "node-gyp rebuild"
  }
}
  • configure 会根据 binding.gypbuild 文件夹下生成当前平台的 C/C++ 工程 - 第一步执行这个
    image

ps: 下面的命令干的活都交给 VS 咯,不要去折腾命令咯(除非你没有VS)

  • build(可选) 如果你不想用 VS,只是编译已有的 C/C++ 程序,那么这条命令可以代替 VS 帮你构建 - 需要 windows-build-tools
  • clean(可选) 把 build 目录清除
  • rebuild(可选) 依次执行 clean、configure、build 三条命令
  1. 新建 binding.gyp
{
  "targets": [
    {
      "target_name": "hello",
      "sources": [ "src/hello.c" ],
    }
  ]
}
  • 构建配置文件,语法同 js 版本的 json。等价于 webpack.config.js
  • targets 下面的每一项都可以理解为一个 node插件,等价于 webpack 打包 bundle.js
  • target_namerequire([target_name])
  • sources C/C++ 源码目录
  • 更多配置参考
  1. 生成目标平台项目
$ yarn configure
  1. 启动 Visual Studio
    image

    image

编写扩展

  • 一些 API 说明
  napi_status 枚举
    · 调用任意 N-API 后返回值类型
  napi_extended_error_info 结构体
    · 表示调用 N-API 后发生的错误对象
  napi_env 结构体
    · 告诉 N-API 当前执行上下文,在调用 Addons 时自动(Init)传入
    · 调用任意多个、或嵌套 N-API 时候需要一直传递下去,不允许重用
  napi_callback_info 
    · 用于 Addons 获取 js 调用时候传入的上下文信息,如参数
  napi_value 不透明指针
    · N-API 提供的在 C、js 中间的一种数据类型
    · 任意的 js 数据类型都可以赋值给 napi_value,然后通过 N-API 提供的方法再把 napi_value 转成 C 语言的类型,反之亦然
  napi_threadsafe_function 不透明指针
    · 代表一个 js 的 function,在多线程模式下通过 napi_call_threadsafe_function 调用实现异步 😁

  # 函数
  napi_create_string_utf8
    · 创建 napi 类型的 string
    · 相当于 const str = 'Hello world!'
  napi_get_property
    · 从 napi 类型的对象中取值
    · 相当于对 json = { name: 'anan', age: 29 } 取值: console.log(json.name, json.age)

  napi_get_cb_info
    · **** 这个可以说是最重要的 API 了,再怎么强调也不为过 ****
    · 用于获取 js 的入参;如原始值类型、对象类型,甚至拿到 function 类型的指针地址
    · 可以说是 js 和 N-NAP 之前的桥梁

  napi_call_function
    · Addons 调用 js 回调
  napi_create_function
    · 创建 js 函数
  napi_get_global
    · 在 Addons 中获取 js 的 global 对象

  • C/C++ src/hello.c
#include <stdio.h>
#include <node_api.h>
#include <string.h>

napi_value Hello(napi_env env, napi_callback_info info) {
    size_t argc = 1;         // 只接受一个参数
    napi_value argv;         // 接收到的参数
    char n[40];
    char hello[90] = "Hello ";
    napi_value result;
    napi_get_cb_info(env, info, &argc, &argv, NULL, NULL);                     // 获取接收参数
    napi_get_value_string_utf8(env, argv, n, sizeof(n), NULL);                 // 将接收到的参数转换为 C 语言类型
    napi_create_string_utf8(env, strcat(hello, n), NAPI_AUTO_LENGTH, &result); // 拼接字符串

    return result;
}

napi_value Init(napi_env env, napi_value exports) {
    // 描述 hello 属性
    napi_property_descriptor desc = {
        "hello",
        NULL,
        Hello,
        NULL,
        NULL,
        NULL,
        napi_default,
        NULL };
    // 将 hello 挂载到 exports 上面 === require('hello.node').hello;
    napi_define_properties(env, exports, 1, &desc);

    return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

ps: 编写好C代码后,Ctrl+Shift+b (VS编译快捷键)

  • javascript test/hello.js
// const addon = require('./build/Debug/hello.node'); // 如果 VS 编译模式是 Debug
const addon = require('./build/Release/hello.node'); // 如果 VS 编译模式是 Release

console.log(addon.hello('world!'));
  • 运行
$ node test/hello.js
Hello world!

Boom Shakalaka

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容