前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Node.js Addon实现类继承

使用Node.js Addon实现类继承

作者头像
theanarkh
发布2021-07-30 16:59:28
2K0
发布2021-07-30 16:59:28
举报
文章被收录于专栏:原创分享

前言:昨天有个同学问怎么通过NAPI把C++类的继承关系映射到JS,很遗憾,NAPI貌似还不支持,但是V8支持,因为V8在头文件里导出了这些API,并Node.js里也依赖这些API,所以可以说是比较稳定的。本文介绍一下如何实现这种映射(不确定是否能满足这位同学的需求)。

下面我们看一下Addon的实现。会涉及到V8的一些使用,可以先阅读该文章《一段js理解nodejs中js调用c++/c的过程》。首先看一下基类的实现。

代码语言:javascript
复制
#ifndef BASE_H#define BASE_H#include <stdio.h>#include <node.h>#include <node_object_wrap.h>
using namespace node;
using namespace v8;
class Base: public ObjectWrap {
    public:
        static void New(const FunctionCallbackInfo<Value>& info) {
            // 新建一个对象,然后包裹到info.This()中,后面会解包出来使用
            Base* base =  new Base();
            base->Wrap(info.This());
        }

        static void Print(const FunctionCallbackInfo<Value>& info) {
            // 解包出来使用
            Base* base = ObjectWrap::Unwrap<Base>(info.This());
            base->print();
        }

        void print() {
            printf("base print\n");
        }

        void hello() {
            printf("base hello\n");
        }};
#endif

Node.js提供的ObjectWrap类实现了Wrap和UnWrap的功能,所以我们可以继承它简化封包解包的逻辑。Base类定义了两个功能函数hello和print,同时定义了两个类静态函数New和Print。New函数是核心逻辑,该函数在js层执行new Base的时候会执行并传入一个对象,这时候我们首先创建一个真正的有用的对象,并且通过Wrap把该对象包裹到传进来的对象里。我们继续看一下子类。

代码语言:javascript
复制
#ifndef DERIVED_H#define DERIVED_H#include <node.h>#include <node_object_wrap.h>#include"Base.h"
using namespace node;
using namespace v8;
class Derived: public Base {
    public:
        static void New(const FunctionCallbackInfo<Value>& info) {
            Derived* derived =  new Derived();
            derived->Wrap(info.This());
        }

        static void Hello(const FunctionCallbackInfo<Value>& info) {
            Derived* derived = ObjectWrap::Unwrap<Derived>(info.This());
            // 调用基类的函数
            derived->hello();
        }};
#endif

子类的逻辑类似,New函数和基类的逻辑一样,除了继承基类的方法外,额外定义了一个Hello函数,但是我们看到这只是个壳子,底层还是调用了基类的函数。定义完基类和子类后,我们把这两个类导出到JS。

代码语言:javascript
复制
#include <node.h>#include "Base.h"#include "Derived.h"
namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
using v8::FunctionTemplate;
using v8::Function;
using v8::Number;
using v8::MaybeLocal;
using v8::Context;
using v8::Int32;
using v8::NewStringType;
void Initialize(  Local<Object> exports,
  Local<Value> module,
  Local<Context> context
) {
  Isolate * isolate = context->GetIsolate();
  // 新建两个函数模板,基类和子类,js层New导出的函数时,V8会执行New函数并传入一个对象
  Local<FunctionTemplate> base = FunctionTemplate::New(isolate, Base::New);
  Local<FunctionTemplate> derived = FunctionTemplate::New(isolate, Derived::New);

  // js层使用的类名
  NewStringType type = NewStringType::kNormal;
  Local<String> base_string = String::NewFromUtf8(isolate, "Base", type).ToLocalChecked();
  Local<String> derived_string = String::NewFromUtf8(isolate, "Derived", type).ToLocalChecked();

  // 预留一个指针空间
  base->InstanceTemplate()->SetInternalFieldCount(1);
  derived->InstanceTemplate()->SetInternalFieldCount(1);

  // 定义两个函数模板,用于属性的值
  Local<FunctionTemplate> BasePrint = FunctionTemplate::New(isolate, Base::Print);
  Local<FunctionTemplate> Hello = FunctionTemplate::New(isolate, Derived::Hello);

  // 给基类定义一个print函数
  base->PrototypeTemplate()->Set(isolate, "print", BasePrint);
  // 给子类定义一个hello函数
  derived->PrototypeTemplate()->Set(isolate, "hello", Hello);
  // 建立继承关系
  derived->Inherit(base);
  // 导出两个函数给js层
  exports->Set(context, base_string, base->GetFunction(context).ToLocalChecked()).Check();
  exports->Set(context, derived_string, derived->GetFunction(context).ToLocalChecked()).Check();}
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)}

我们看到给基类原型定义了一个print函数,给子类定义了hello函数。最后我们看看如何在JS层使用。

代码语言:javascript
复制
const { Base, Derived } = require('./build/Release/test.node');const base = new Base();const derived = new Derived();base.print();
derived.hello();
derived.print();
console.log(derived instanceof Base, derived instanceof Derived)

下面是具体的输出

代码语言:javascript
复制
base print
base hello
base print
true true

我们逐句分析

1 base.print()比较简单,就是调用基类定义的Print函数。

2 derived.hello()看起来是调用了子类的Hello函数,但是Hello函数里调用了基类的hello函数,实现了逻辑的复用。

3 derived.print()子类没有实现print函数,这里调用的是基类的print函数,和1一样。

4 derived instanceof Base, derived instanceof Derived。根据我们的定义,derived不仅是Derived的实例,也是Base的实例。

实现代码分析完了,我们看到把C++类映射到JS的方式有两种,第一种就是两个C++ 类没有继承关系,通过V8的继承API实现两个JS层存在继承关系的类(函数),比如print函数的实现,我们看到子类没有实现print,但是可以调用print,因为基类定义了,Node.js就是这样处理的。第二种就是两个存在继承关系的C++类,同样先通过V8的API实现两个继承的类导出到JS使用,因为JS层使用的只是壳子,具体执行到C++代码的时候,我们再体现出这种继承关系。比如Hello函数的实现,虽然我们是在子类里导出了hello函数,并且JS执行hello的时候的确执行到了子类的C++代码,但是最后会调用基类的hello函数。

最后我们通过Nodej.js看看是如何做这种映射的,我们通过PipeWrap.cc的实现进行分析。

代码语言:javascript
复制
// 新建一个函数模板
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);// 继承两个函数模板
t->Inherit(LibuvStreamWrap::GetConstructorTemplate(env));// 导出给JS使用
exports->Set(env->context(),
              pipeString,
              t->GetFunction(env->context()).ToLocalChecked()).Check();

上面代码实现了继承,我们看看GetConstructorTemplate的实现。

代码语言:javascript
复制
tmpl = env->NewFunctionTemplate(nullptr);
env->SetProtoMethod(tmpl, "setBlocking", SetBlocking);
env->SetProtoMethod(tmpl, "readStart", JSMethod<&StreamBase::ReadStartJS>);
env->SetProtoMethod(t, "readStop", JSMethod<&StreamBase::ReadStopJS>);// ...

上面代码新建了一个新的函数模板并且设置了一系列的原型属性,那么模板t就继承了这些属性。我们看看Node.js里怎么使用的。

代码语言:javascript
复制
function createHandle(fd, is_server) {
  // ...
  return new Pipe(
      is_server ? PipeConstants.SERVER : PipeConstants.SOCKET
  );}

this._handle = createHandle(fd, false);
err = this._handle.setBlocking(true);

上面的代码首先会创建一个Pipe对象,然后调用它的setBlocking方法。我们发现Pipe(pipe_wrap.cc)是没有实现setBlocking函数的,但是好为什么他可以调用setBlocking呢?答案就是它的基类实现了。

后记:在JS里实现继承是简单的,但是在底层实现起来还是比较复杂的,但是从代码设计的角度来看是非常有必要的。

代码可以在仓库获取:

https://github.com/theanarkh/learn-to-write-nodejs-addons。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档