0

0

C/C++写node.js原生模块详解

零下一度

零下一度

发布时间:2017-07-09 10:18:55

|

2306人浏览过

|

来源于php中文网

原创

这篇文章主要给大家介绍了关于利用c/c++编写node.js原生模块的相关资料,文中将实现的步骤一步步的介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面跟着小编来一起看看吧。

前言

一直想了解一下使用C/C++编写nodejs原生模块,从网上找到的博客,大多都停留在如何搭建环境,然后一个Hello World完事。连更多的参考资料也没有。于是就自己整理了一下,分享于此。

至于准备环境什么的,网上一抓一大把,就不再详述 。

主要参考两个地方:

立即学习C++免费学习笔记(深入)”;

  • nodejs官方文档

  • v8文档

其中第一个是nodejs的官方文档,里面介绍了几个不错的参考例子。

第二个是v8引擎的文档,c++的,编写c++模块主要看这个文档。

好了,我们开始几个例子,逐步的了解如何使用c++编写nodejs模块。

Hello World

不能免俗,第一个先上来写个Hello World吧,毕竟程序员认识的第一个程序就是Hello World。


#include <node.h>
void hello(const v8::FunctionCallbackInfo<v8::Value> &args) {
 v8::Isolate *isolate = args.GetIsolate();
 auto message = v8::String::NewFromUtf8(isolate, "Hello World!");
 args.GetReturnValue().Set(message);
}
void Initialize(v8::Local<v8::Object> exports) {
 NODE_SET_METHOD(exports, "hello", hello);
}
NODE_MODULE(module_name, Initialize)

好了,这是最简单的一个HelloWorld,我们将文件命名为addon.cc,我们使用node-gyp编译一下,然后在我们的js文件中直接使用require引入模块,然后就可以调用了。


const myAddon = require('./build/Release/addon') ;
console.log(myAddon.hello());

如无意外,将会在终端打印Hello World!。

我们简单来看一下代码,第一行#include <node.h>是C++中引入node.h头文件的代码。头文件可理解为接口,我们在里面只定义了接口方法,并未实现,然后通过其他文件实现,C++链接器负责将这两个链接在一起。

然后定义了一个方法hello() ,没有返回值。方法参数通过const v8::FunctionCallbackInfo<v8::Value> &args传递,注意,这里我们加了v8::前缀注解,也可以直接在文件开始使用using v8;这样就可以不用每次都使用这个注解了。

v8::Isolate *isolate = args.GetIsolate();这里,我们在函数中访问了javascript的作用域。

auto message = v8::String::NewFromUtf8(isolate, "Hello World!");我们创建了一个字符串类型的变量,赋值Hello World!并将其绑定到作用域。

我们通过args.GetReturnValue()获取了我们函数的返回值

Initialize()方法用于初始化模块方法,将方法和要导出的模块的方法名进行绑定。

最后NODE_MODULE导出这个模块。

上面这个例子很简单,如果是js代码的话:


'use strict';
let hello = function hello() {
 let message = "Hello World!";
 return message;
};
module.exports = {
 hello:hello
};

好了,第一个HelloWorld就结束了。网上很多介绍nodejs C++模块的博客文章,到这里就结束了。看完之后,一脸懵逼,啥啊这是?我想再写个传参数,并对参数做简单操作的方法该怎么写?

sum(a,b)

好吧。那我们就再写一个sum(a,b)函数,传递两个数字类型参数a,b,并求两个参数的和返回。

js中代码简单到下:


let sum = function(a,b){
 if(typeof a == 'number' && typeof b == 'number'){
  return a + b;
 }else{
  throw new Error('参数类型错误');
 }
}

那么,C++该如何编写:


void sum(const FunctionCallbackInfo<Value> &args) {
 Isolate *isolate = args.GetIsolate();
 if(!args[0]->IsNumber()){
  isolate->ThrowException(v8::Exception::TypeError(
   v8::String::NewFromUtf8(isolate, "args[0] must be a number")));
  return ;
 }
 if(!args[1]->IsNumber()){
  isolate->ThrowException(v8::Exception::TypeError(
   v8::String::NewFromUtf8(isolate, "args[1] must be a number")));
  return ;
 }
 args.GetReturnValue().Set(args[0]->NumberValue() + args[1]->NumberValue());
}

首先判断两个参数是否是Number类型,如果不是,直接抛出异常。如果是,则将返回值设置为两个参数的和。

这里我们并没有在参数列表中,直接使用a,b作为参数,而是直接使用 args 对象。 这和js是类似的,第一个参数是 args[0] ,第二个参数是 args[1]

调用IsNumber()来判断是否是数字类型。如果不是,抛出一个TypeError类型错误异常。
如果类型没问题,使用args[0]->NumberValue()获取参数的数字值,然后相加,赋值给返回值。

可能你会问,args[0] 这是个啥?它的IsNumber()方法又是怎么来的?哪里有文档可以查阅呢?

这里其实是v8引擎内部类型,基本和js的内置对象是一一对应的。可以查阅v8类型说明文档。

上面这个图是不是很熟悉,和js的类型系统特别像。

js的Array,Date,Function,String等等都是继承自Object,而v8引擎内部,Object和Primitive都是继承自Value类型。

这里的IsNumber()方法就是Value类型的方法。那么除了这个方法,还有什么方法呢?

上面这张图,我只是截了一小部分,全部的可以直接去查阅文档。看,这里有各种方法,判断是否是数字类型的IsNumber(),判断是否是日期类型的IsDate() ,判断是否是数组的IsArray()方法等等。

Mokker AI
Mokker AI

AI产品图添加背景

下载

v8的接口实现的也很完善了,即使并不精通C++的开发者也可以照猫画虎的实现个简单的模块。

args[0]->NumberValue()返回的是一个double的值,是的,这里是实打实的C++里的double类型,可以直接进行加减运算的。类似的还有BooleanValue()方法等等,都是获取不同类型的值使用的方法。

第二个例子中,我们简单实现了一个sum()方法,传递两个参数,求和。但是这里涉及到的只是整型的值,那如果有其他类型的值怎么办呢?比如数组。

sumOfArray(array)

下面将方法升级一下,传递一个数组,然后求数组中所有值的和。js的话:


let sumOfArray = function(array){
 if(!Array.isArray(array)){
  throw new Error('参数错误,必须为Array类型');
 }
 let sum = 0;
 for(let item of array){
  sum += item;
 }
 return sum;
}

逻辑很简单,就是将传过来的数组进行遍历一遍,然后将所有项累加即可。C++也是如此:


void sumOfArray(const FunctionCallbackInfo<Value> &args){
 Isolate *isolate = args.GetIsolate();
 if(!args[0]->IsArray()){
  isolate->ThrowException(v8::Exception::TypeError(
   v8::String::NewFromUtf8(isolate, "args[0] must be an Array")));
  return ;
 }
 Local<Object> received_v8_obj = args[0]->ToObject();
 Local<Array> keys = received_v8_obj->GetOwnPropertyNames();
 int length = keys->Length();
 double sum = 0;
 for(int i=0;i<length;i++){
  sum += received_v8_obj->Get(keys->Get(i))->NumberValue();
 }
 args.GetReturnValue().Set(sum);
}

先判断是否是数组,没什么问题。

然后我们定义了一个Object类型的received_v8_obj属性,将其赋值为args[0]->ToObject() 。这里调用ToObject()方法将其转换为一个对象。

然后调用这个对象的GetOwnPropertyNames()方法获取所有的键,然后根据键获取对象的值,进行累加。

为什么不直接将其转换为数组,然后进行遍历呢?

我们都知道,js中的数组并不是真正的数组,其实质还是对象。其内部都是键值对存储的。因此这里也是一样,Value类型并不提供直接转换为数组的ToArray()方法,而是将其转换为Object对象,通过对象的形式进行操作。

那么对象有哪些操作呢,看文档。

但是你会发现,v8确实有个Array类,继承自Object类。那么Array有什么方法呢?

看文档就知道了,少的可怜:

所以,对数组的操作都将转换为对象操作。

createObj()

说到对象了,那么我们就来写一个创建对象的方法。传递两个参数,一个name,一个age,创建一个对象,表示一个人,名叫啥,多大年纪。


void createObj(const FunctionCallbackInfo<Value> &args){
  Isolate *isolate = args.GetIsolate();
  Local<Object> obj = Object::New(isolate);
  obj->Set(String::NewFromUtf8(isolate,"name"),args[0]->ToString());
  obj->Set(String::NewFromUtf8(isolate,"age"),args[1]->ToNumber());
  args.GetReturnValue().Set(obj);
}

这个方法,参照文档,基本没啥可说的。

通过Object::New(isolate)创建一个对象,然后设置两个属性name,age,将参数依次赋值给这两个属性,然后返回这个对象即可。

如果用js写:


let createObj = function(name,age){
  let obj = {};
  obj.name = name;
  obj.age = age;
  return obj;
};

callback

上面说的,都没提到js中一个重要的东西,回调函数。如果参数中传一个回调函数,那么我们该如何执行呢?

来一个简单的例子。


let cb = function(a,b,fn){
  if(typeof a !== 'number' || typeof b !== 'number'){
    throw new Error('参数类型错误,只能是Number类型');
  }
  if(typeof fn !== 'function'){
    throw new Error('参数fn类型错误,只能是Function类型');
  }
  fn(a,b);
};

这个例子很简单,我们传两个数字类型参数a,b和一个回调函数fn,然后将a,b作为fn的参数调用fn回调函数。这里我们对a,b的操作转交给回调函数。回调函数里我们可以求和,也可以求积,随你。

这个例子中,暂时还没涉及到的是如何调用回调函数。

先上代码:


void cb(const FunctionCallbackInfo<Value> &args){
  Isolate *isolate = args.GetIsolate();
  if(!args[0]->IsNumber()){
    isolate->ThrowException(v8::Exception::TypeError(
      v8::String::NewFromUtf8(isolate, "args[0] must be a Number")));
  }
  if(!args[1]->IsNumber()){
    isolate->ThrowException(v8::Exception::TypeError(
      v8::String::NewFromUtf8(isolate, "args[1] must be a Number")));
  }
  if(!args[2]->IsFunction()){
    isolate->ThrowException(v8::Exception::TypeError(
      v8::String::NewFromUtf8(isolate, "args[2] must be a Function")));
  }
  Local<Function> jsfn = Local<Function>::Cast(args[2]);
  Local<Value> argv[2] = { Number::New(isolate,args[0]->NumberValue()),Number::New(isolate,args[1]->NumberValue())};
  Local<Value> c = jsfn->Call(Null(isolate),2,argv);
  args.GetReturnValue().Set(c);
}

上面三个判断参数类型,略过。

我们定义一个Function类型属性jsfn,将args[2]强制转换为Function并赋值给jsfn。

然后定义一个具有两个值的参数argv,这两个值就是args[0] , args[1]的数字值。

然后通过jsfn->Call(Null(isolate),2,argv)调用回调函数。

argv是一个数组,其个数我们在定义时指定,2个。

Call()方法为函数类型的值进行调用的方法。

Local< Value > | Call (Handle< Value > recv, int argc, Handle< Value > argv[])

查阅文档,可以看出,Call()方法传3个参数,第一个参数是执行上下文,用于绑定代码执行时的this,第二个参数为参数个数,第三个为参数列表,数组形式。

上面几个例子,只是冰山一角,连一角都算不上。只为了解一下nodejs使用C/C++编写原生模块,如果要编写一个可用的,高性能的C模块,那么,要求程序员一定要精通C/C++,并且对js底层也很精通,包括v8和libuv等等。

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

46

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

178

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

51

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

532

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

171

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号