拓展nodejs的方式有很多种,可以写npm包,可以写c++插件,还可以修改内核重新编译分发。本文介绍如何通过为nodejs内核增加一个c++模块的方式拓展nodejs的功能(git地址:https://github.com/theanarkh/learn-how-to-extend-node )。相比修改nodejs内核代码,新增一个nodejs内置模块需要了解更多的知识。下面我们开始。 1 首先在src文件夹下新增两个文件。 cyb.h
#ifndef SRC_CYB_H_
#define SRC_CYB_H_
#include "v8.h"
namespace node {
class Environment;
class Cyb {
public:
static void Initialize(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv);
private:
static void Console(const v8::FunctionCallbackInfo<v8::Value>& args);
};
} // namespace node
#endif
#include "cyb.h"
#include "env-inl.h"
#include "util-inl.h"
#include "node_internals.h"
namespace node {
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
void Cyb::Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
// 申请一个函数模块,模板函数是Console
Local<FunctionTemplate> t = env->NewFunctionTemplate(Console);
// 申请一个字符串
Local<String> str = FIXED_ONE_BYTE_STRING(env->isolate(), "console");
// 设置函数类名
t->SetClassName(str);
// 导出函数,target即exports
target->Set(env->context(),
str,
t->GetFunction(env->context()).ToLocalChecked()).Check();
}
void Cyb::Console(const FunctionCallbackInfo<Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::Local<String> str = String::NewFromUtf8(isolate, "hello world");
args.GetReturnValue().Set(str);
}
} // namespace node
// 声明该模块
NODE_MODULE_CONTEXT_AWARE_INTERNAL(cyb_wrap, node::Cyb::Initialize)
我们新定义一个模块,是不能自动添加到nodejs内核的。我们还需要额外的操作。我们需要修改node.gyp文件。把我们新增的文件加到配置里,否则编译的时候,不会编译这个新增的模块。我们可以在node.gyp文件中找到src/tcp_wrap.cc,然后在他后面加入我们的文件就行。
src/cyb_wrap.cc
src/cyb_wrap.h
这时候nodejs会编译我们的代码了。但是nodejs的内置模块有一定的机制,我们的代码加入了nodejs内核,不代表就可以使用了。我们看一下nodejs对内置++ 模块的机制。nodejs在初始化的时候会调用RegisterBuiltinModules函数注册所有的内置c++模块。
void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
NODE_BUILTIN_MODULES(V)
#undef V
}
我们看到该函数只有一个宏。我们看看这个宏。
#define NODE_BUILTIN_MODULES(V) \
NODE_BUILTIN_STANDARD_MODULES(V) \
NODE_BUILTIN_OPENSSL_MODULES(V) \
NODE_BUILTIN_ICU_MODULES(V) \
NODE_BUILTIN_REPORT_MODULES(V) \
NODE_BUILTIN_PROFILER_MODULES(V) \
NODE_BUILTIN_DTRACE_MODULES(V)
宏里面又是一堆宏,本文不是源码解析,所以不深入讲解,可以参考之前的文章。我们要做的就是修改这个宏。因为我们是自定义的内置模块,所以我们可以增加一个宏。
#define NODE_BUILTIN_EXTEND_MODULES(V) \
V(cyb_wrap)
然后把这个宏追加到那一堆宏后面。
#define NODE_BUILTIN_MODULES(V) \
NODE_BUILTIN_STANDARD_MODULES(V) \
NODE_BUILTIN_OPENSSL_MODULES(V) \
NODE_BUILTIN_ICU_MODULES(V) \
NODE_BUILTIN_REPORT_MODULES(V) \
NODE_BUILTIN_PROFILER_MODULES(V) \
NODE_BUILTIN_DTRACE_MODULES(V) \
NODE_BUILTIN_EXTEND_MODULES(V)
这时候,nodejs不仅可以编译我们的代码,还把我们的代码定义的模块注册到内置c++模块里了。接下来就是如何使用c++模块了。 2 在lib文件夹新建一个cyb.js,作为nodejs原生模块
const cyb = internalBinding('cyb_wrap');
module.exports = cyb;
internalBinding函数是在执行cyb.js,注入的函数。不能在用户js里使用。internalBinding函数就是根据模块名从内置模块里找到对应的模块。即我们的cyb.cc。新增原生模块,我们也需要修改node.gyp文件,否则代码也不会被编译进node内核。我们找到node.gyp文件的lib/net.js。在后面追加lib/cyb.js。该配置下的文件是给js2c.py使用的。如果不修改,我们在require的时候,就会找不到该模块。最后我们在lib/internal/bootstrap/loader文件里找到internalBindingWhitelist变量,在数组最后增加cyb_wrap。这个配置是给process.binding函数使用的,如果不修改这个配置,通过process.binding就找不到我们的模块。process.binding是可以在用户js里使用的。
到此,我们完成了所有的修改工作,重新编译nodejs。然后编写测试程序。 3 新建一个测试文件testcyb.js
// const cyb = process.binding('cyb_wrap');
const cyb = require('cyb');
console.log(cyb.console())
输出hello world。
总结:本文介绍如何给nodejs内核新增一个新的模块。学习修改nodejs内核是成为nodejs contributor的第一步,结合v8和libuv的知识。你可以为nodejs做得更多,
👍