拓展nodejs内核
发布于 4 年前 作者 theanarkh 3178 次浏览 来自 分享

拓展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

cyb.cc

#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做得更多,

1 回复
回到顶部