medooze源码分析--NodeJS调用C/C++
NodeJs Native扩展的基本知识
简单来说,我们可以说Native扩展是一组从JavaScript代码调用C++实现的逻辑。在这一点上,对我们来说,最有趣的是将NodeJS是如何工作的以及它涉及那些部分讲清楚。 重要的是要知道为什么我们可以在NodeJS中使用两种语言(JavaScript和C ++)。
可以这样解释:
- JavaScript:它是编码语言。
- V8:它是运行我们的JavaScript代码的引擎。
- Libuv:它是一个为我们提供异步执行的C库。
现在,我们将选择写/读磁盘作为示例来解释它。 JavaScript和V8都没有为我们提供磁盘访问。 Libuv提供的是异步执行,也没有为我们提供磁盘操作。 但是,使用NodeJS,我们却可以写入/读取磁盘,对吧?这就是 Native 扩展的关键点。 fs模块使用C ++(它具有磁盘访问权限)实现了对文件的读写操作,并公开了从JavaScript调用的方法(如writeFile和readFile)。
一旦我们了解了这一点,我们就可以开始在Native扩展中迈出第一步了。 我们来谈谈我们需要的工具。
编译 Native 扩展的工具
BINDING.GYP 文件
该文件允许我们指定编译Native扩展的方式。 我们需要定义的主要内容是指定要编译的文件以及我们如何调用最终的二进制文件。 它具有类似JSON的结构,获得此配置的关键是源和目标。
NODE-GYP
它是允许我们编译 Native 扩展的工具。 它在Node.js中实现,它与npm捆绑在一起,所以我们可以运行npm install来编译Native扩展。 当我们运行npm install时,它将检测我们的根文件夹中包含的binding.gyp文件,然后开始编译。
此外,它允许我们 build release(默认)版本或 debug 版本。 因此,将在release或debug文件夹中创建具有.node扩展名的二进制文件,具体取决于其配置方式。
BINDINGS
BINDING是一个Node.js包,允许我们导出Native扩展。 它负责在build或release目录里为我们搜索Native扩展。
N-API
它是C API,允许我们以完全抽象的方式与我们的引擎交互。 对我来说,这是尝试将Node 移植到不同架构的演变的结果。
N-API提供不同Node版本之间的稳定性和兼容性。 也就是说,如果我的Native扩展在Node 8.1上被编译,我就不需要再为Node 8.6或9.3编译它。 从而使维护者和贡献者更轻松。
Native扩展的第一步
我们将使用经典的hello world示例开启Native扩展的讲解。 我们的想法是不要用额外的逻辑重载代码,这样我们就可以专注于最少的必要代码。
我们开始初始化npm,以便我们可以安装我们的依赖项:
npm init
现在,正如我们所说,我们安装了依赖项:
npm i node-addon-api bindings
此时我们需要用我们的逻辑创建我们的C文件:
#include <napi.h>
Napi::String SayHi(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::String::New(env, "Hi!");
}
Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "sayHi"), Napi::Function::New(env, SayHi));
return exports;
};
NODE_API_MODULE(hello_world, init);
该文件有三个重要部分,将从下到上进行解释:
-
NODE_API_MODULE(第14行):第一个参数是Native扩展的名子,第二个参数是Native扩展的初始化函数。
-
Init(第10行):这是初始化Native扩展函数。 在这个函数中,我们必须导出JavaScript代码将要调用的函数。 为此,我们需要把将被调用的函数名设置到exports 对象中。 此外,init函数必须返回 exports 对象。
-
SayHi(第3行):当我们从JavaScript调用Native扩展时,将执行此功能。
稍后,我们需要创建包含我们的Native扩展配置的binding.gyp文件:
{
"targets": [
{
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"include_dirs" : [
"<!@(node -p \"require('node-addon-api').include\")"
],
"target_name": "hello_world",
"sources": [ "hello_world.cc" ],
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]
}
]
}
最后,JavaScript代码将require我们的扩展并调用它。
const hello_world = require('bindings')('hello_world')
console.log(hello_world.sayHi());
现在,我们只需要编译运行npm install的扩展并运行使用的JavaScript文件:
就是这样。 我们只是运行我们的Native扩展。
我们在N-API之前做了什么?
我发现了解Native扩展的上下文和历史很重要,因为它可以访问大量文档和示例。 这个想法是让N-API最终取代NAN。 出于这个原因,我们应该回顾一下NAN。
NAN? 是的,Node.js的Native抽象。 NAN是一个为我们提供V8抽象的C ++库,但它不允许我们从V8中抽象出自己。
在新的NodeJS版本中,可能存在可能破坏我们的Native扩展的V8更改。 使用N-API是一种避免此问题的方法。
进一步开发Native扩展的步骤
正如我所说,了解NAN允许我们从其示例和文档中学习。 它是我们Native扩展学习过程的一个很好的补充。
- NAPI示例可以在这里找到。
- Node-addon-api示例可以在这里找到。
- 可以在这里找到Nan示例。
- 另一个好的来源是这里的测试。
- 要了解有关原生扩展的更多信息。
小结
了解 Native 扩展有助于我了解NodeJS的工作原理及其组成方式。 我们可以使用多种方案,例如性能提升,C / C ++库集成或与遗留代码的集成。
总之,这是了解NodeJS内部的一种很好的方法。