网易首页 > 网易号 > 正文 申请入驻

使用Node.js Addon实现类继承

0
分享至

作者: theanarkh 来源: 编程杂技

本文转载自微信公众号「编程杂技」,作者theanarkh。转载本文请联系编程杂技公众号。

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

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

#ifndef BASE_H
#define BASE_H
#include
#include
#include
using namespace node;
using namespace v8;
class Base: public ObjectWrap {
public:
static void New(const FunctionCallbackInfo
& info) {
// 新建一个对象,然后包裹到info.This()中,后面会解包出来使用
Base* base = new Base();
base->Wrap(info.This());
static void Print(const FunctionCallbackInfo
& info) {
// 解包出来使用
Base* base = ObjectWrap::Unwrap(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把该对象包裹到传进来的对象里。我们继续看一下子类。

#ifndef DERIVED_H
#define DERIVED_H
#include
#include
#include"Base.h"
using namespace node;
using namespace v8;
class Derived: public Base {
public:
static void New(const FunctionCallbackInfo
& info) {
Derived* derived = new Derived();
derived->Wrap(info.This());
static void Hello(const FunctionCallbackInfo
& info) {
Derived* derived = ObjectWrap::Unwrap
(info.This());
// 调用基类的函数
derived->hello();
#endif

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

#include
#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 exports,
Local
module,
Local
context
) {
Isolate * isolate = context->GetIsolate();
// 新建两个函数模板,基类和子类,js层New导出的函数时,V8会执行New函数并传入一个对象
Local
base = FunctionTemplate::New(isolate, Base::New);
Local
derived = FunctionTemplate::New(isolate, Derived::New);
// js层使用的类名
NewStringType type = NewStringType::kNormal;
Local"Base", type).ToLocalChecked();
base_string = String::NewFromUtf8(isolate,
Local"Derived", type).ToLocalChecked();
derived_string = String::NewFromUtf8(isolate,
// 预留一个指针空间
base->InstanceTemplate()->SetInternalFieldCount(1);
derived->InstanceTemplate()->SetInternalFieldCount(1);
// 义两个函数模板,用于属性的值
Local
BasePrint = FunctionTemplate::New(isolate, Base::Print);
Local
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层使用。

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)

下面是具体的输出

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的实现进行分析。

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

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

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里怎么使用的。

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。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
14年儿子坐马航失联,多年后父亲收到回信,坚信儿子去了平行世界

14年儿子坐马航失联,多年后父亲收到回信,坚信儿子去了平行世界

史行途
2026-01-29 12:13:30
61岁男子,坚持饿肚子不吃晚饭,6个月之后,血糖和体重情况如何

61岁男子,坚持饿肚子不吃晚饭,6个月之后,血糖和体重情况如何

蜉蝣说
2026-02-03 15:04:01
六脉神剑,放弃高位,他是巴萨最锋利的剑

六脉神剑,放弃高位,他是巴萨最锋利的剑

西哇体育
2026-02-24 10:58:03
正式告别?谷爱凌发声,宣布决定,或长期定居美国,新计划曝光

正式告别?谷爱凌发声,宣布决定,或长期定居美国,新计划曝光

草莓解说体育
2026-02-24 04:18:42
房子够住14亿人两遍,为何年轻人却买不起?中国房地产30年真相

房子够住14亿人两遍,为何年轻人却买不起?中国房地产30年真相

流苏晚晴
2026-02-11 18:25:16
尼格买提发视频晒家宴,8个菜没有一个青菜,新疆网友:典型配置

尼格买提发视频晒家宴,8个菜没有一个青菜,新疆网友:典型配置

韩小娱
2026-02-23 20:30:29
1-0!卡里克神换人,谢什科一剑封喉,曼联复仇,剑指英超前三

1-0!卡里克神换人,谢什科一剑封喉,曼联复仇,剑指英超前三

我的护球最独特
2026-02-24 05:57:07
春晚过去1周,王菲争议持续升级!央媒18字一针见血,字字戳心窝

春晚过去1周,王菲争议持续升级!央媒18字一针见血,字字戳心窝

削桐作琴
2026-02-23 22:08:55
为秦昊拼三胎!58岁伊能静怀三胎,已怀孕5个月是男宝,计划赴美生产

为秦昊拼三胎!58岁伊能静怀三胎,已怀孕5个月是男宝,计划赴美生产

八卦王者
2026-02-22 13:23:33
回顾:四川一男子阴茎撕裂入院,9天后复查结果如何了

回顾:四川一男子阴茎撕裂入院,9天后复查结果如何了

新时代的两性情感
2026-02-23 18:01:52
WTT战报:女单爆大冷,奥运亚军1:3被淘汰,蒯曼、石洵瑶零封

WTT战报:女单爆大冷,奥运亚军1:3被淘汰,蒯曼、石洵瑶零封

萌萌运动荟
2026-02-23 23:08:16
从“天才少女”跌落神坛后,17岁的姜萍如今在服装厂踩缝纫机?一家人去向成谜

从“天才少女”跌落神坛后,17岁的姜萍如今在服装厂踩缝纫机?一家人去向成谜

阿芒娱乐说
2026-02-21 23:50:14
7家美国企业上榜,中国大陆芯片公司全军覆没,这份榜单让人清醒

7家美国企业上榜,中国大陆芯片公司全军覆没,这份榜单让人清醒

科技专家
2026-02-23 16:15:14
斯坦福大学竟然开了个 AI 编程课?!我已经学上了

斯坦福大学竟然开了个 AI 编程课?!我已经学上了

程序员鱼皮
2026-02-23 16:25:03
博德闪耀主帅:曼城都没抱怨人工草坪,老拿这个说事不太聪明

博德闪耀主帅:曼城都没抱怨人工草坪,老拿这个说事不太聪明

懂球帝
2026-02-24 07:49:06
为何保险卖不动了?保险卖不动的原因是当年的骗局到现在已被证实

为何保险卖不动了?保险卖不动的原因是当年的骗局到现在已被证实

来科点谱
2026-02-24 07:11:24
发生了什么?郑钦文社媒单方面取关里巴并更改头像与简介

发生了什么?郑钦文社媒单方面取关里巴并更改头像与简介

懂球帝
2026-02-24 10:27:10
辞去西湖大学教职,他想去中学教书

辞去西湖大学教职,他想去中学教书

返朴
2026-02-24 10:30:19
集腋成裘:乌克兰F-16升空执行拦截任务“末日飞机”驾驶舱曝光

集腋成裘:乌克兰F-16升空执行拦截任务“末日飞机”驾驶舱曝光

hawk26讲武堂
2026-02-23 11:11:19
央视重磅官宣:歼16单机双锁两架F-22,外军隐身战机吓得再不敢来

央视重磅官宣:歼16单机双锁两架F-22,外军隐身战机吓得再不敢来

52赫兹实验室
2026-02-23 12:43:04
2026-02-24 12:36:49
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 823关注度
往期回顾 全部

科技要闻

AI颠覆发展最新牺牲品!IBM跳水重挫超13%

头条要闻

牛弹琴:白宫突然发了张图 伤害性不大侮辱性极强

头条要闻

牛弹琴:白宫突然发了张图 伤害性不大侮辱性极强

体育要闻

苏翊鸣总结米兰征程:我仍是那个热爱单板滑雪的少年

娱乐要闻

杨洋传遇上缅北剧组 开机就离开剧组?

财经要闻

商务部将20家日本实体列入关注名单

汽车要闻

淦家阅定调价值战 吉利高阶智驾加速普及

态度原创

时尚
手机
游戏
家居
军事航空

今年春天一定要拥有的针织,这样穿减龄又好看!

手机要闻

三星Galaxy S26 Ultra旗舰手机防窥屏原理曝光

天龙官方搞事!土豪砸1800万战半年,现在送保时捷开百团大战

家居要闻

本真栖居 爱暖伴流年

军事要闻

美军重兵集结蓄力作战之际 新一轮美伊谈判时间“敲定”

无障碍浏览 进入关怀版