详解Node.js API系列C/C++ Addons(3) 程序实例

http://blog.whattoc.com/2013/09/08/nodejs_api_addon_3/

再续前文,前文介绍了node.js 的addon用法和google v8 引擎,下面,我们进入真正的编码,下面将会通过六个例子,学习node addon
范例,了解addon编程的特性

  • 创建一个空项目

  • 随机数模块

  • 向模块传递参数

  • 回调函数处理

  • 线程处理

  • 对象管理

创建一个空项目

vi modulename.cpp

#include <node.h>
void RegisterModule(v8::Handle<v8::Object> target) {
// 注册模块功能,负责导出接口到node.js
}
// 注册模块名称,编译后,模块将编译成modulename.node文件
// 当你需要修改模块名字的时候,需要修改 binding.gyp("target_name") 和此处
NODE_MODULE(modulename, RegisterModule);

vi binding.gyp

{
"targets": [
{
"target_name": "modulename",
"sources": [ "modulename.cpp" ]
}
]
}

vi run.js

var modulename = require(‘./build/Release/modulename‘);
console.warn(modulename);

编译addon模块,编译器前,需要确保安装好node-gyp

#npm node-gyp -g
#node-gyp configure
#node-gyp build
#node run.js
{}

随机数模块实现

vi modulename.cpp

#include <node.h>
// 标准C库
#include <cstdlib>
#include <ctime>
using namespace v8;
// 函数返回javascript格式的 0 或 1
Handle<Value> Random(const Arguments& args) {

// 在每一个功能函数中,都会看到 Handle scope,主要是存放handle的容器
// 管理handle的内存空间,当handle scope被销毁的时候
// 内存将会释放,否则内存将会泄漏

HandleScope scope;

// 确保函数中,在调用scope.Close()前都完成了出来
// 当执行完scope.Close后,handle将会被清空。

return scope.Close(
// 将rand() % 2 的C语言int运行结果,转换成javascript类型的结果
Integer::New(rand() % 2)
);
}
void RegisterModule(Handle<Object> target) {
srand(time(NULL));
// target 是 .node 文件导出的模块
target->Set(String::NewSymbol("random"),
FunctionTemplate::New(Random)->GetFunction());
}
NODE_MODULE(modulename, RegisterModule);


HandleScope
scope,申请一个装载Handle(Local)的容器,当生命周期结束时,即scope.Close()的时候,Handle的内容就会被释放。Integer::New(rand()
% 2)
,rand()是C语言编写的函数,产生的数值是C语言类型的,如果node模块需要调用,必须转换格式,Integer::New()
整形转换。最后,函数声明的返回类型是Handle

String, Number, Boolean, Object, Array 继承了Value,想了解更多关于数据类型的转换可以参考《Node.js C++ addon编写实战(二)之对象转换》

vi binding.gyp

{
"targets": [
{
"target_name": "modulename",
"sources": [ "modulename.cpp" ]
}
]
}

vi run.js

var modulename = require(‘./build/Release/modulename‘);
console.warn(modulename.random());

编译执行

#node-gyp configure
#node-gyp build
#node run.js
1 //or 0

函数参数传入

vi modulename.cpp

#include <node.h>
using namespace v8;
// 返回 第N个数 的斐波拉契数列
// argument passed.
Handle<Value> Fibonacci(const Arguments& args) {
HandleScope scope;
//检查参数个数问题
if (args.Length() < 1) {
return ThrowException(
Exception::TypeError(String::New("First argument must be a number"))
);
}
//将javascript格式转换成C语言可以使用的int32_t类型
Local<Integer> integer = args[0]->ToInteger();
int32_t seq = integer->Value();
// 检测seq是否在正确值域上
if (seq < 0) {
return ThrowException(Exception::TypeError(String::New(
"Fibonacci sequence number must be positive")));
}
// 执行算法
int32_t current = 1;
for (int32_t previous = -1, next = 0, i = 0; i <= seq; i++) {
next = previous + current;
previous = current;
current = next;
}
// 返回数值
return scope.Close(Integer::New(current));
}
void RegisterModule(Handle<Object> target) {
target->Set(String::NewSymbol("fibonacci"),
FunctionTemplate::New(Fibonacci)->GetFunction());
}
NODE_MODULE(modulename, RegisterModule);

vi binding.gyp

{
"targets": [
{
"target_name": "modulename",
"sources": [ "modulename.cpp" ]
}
]
}

参数传入的情况,少不参数的合法性校验和类型转换,总结一下几种转换和类型合法性的检测

类型判断

Local<Value> arg = args[0];
bool isArray = arg->IsArray();
bool isBoolean = arg->IsBoolean();
bool isNumber = arg->IsNumber();
bool isInt32 = arg->IsInt32();

类型转换

Local<Value> arg = args[0];
Local<Object> = arg->ToObject();
Local<Boolean> = arg->ToBoolean();
Local<Number> = arg->ToNumber();
Local<Int32> = arg->ToInt32 ();
Local<Function> callback = Local<Function>::Cast(args)

vi run.js

var modulename = require(‘./build/Release/modulename‘);
console.warn(modulename.fibonacci(9));

编译执行

#node-gyp configure
#node-gyp build
#node run.js
32

回调函数处理

vi modulename.cpp

#include <node.h>
using namespace v8;
Handle<Value> Callback(const Arguments& args) {
HandleScope scope;
// Ensure that we got a callback. Generally, your functions should have
// optional callbacks. In this case, you can declare an empty
// Local<Function> handle and check for content before calling.

//确保参数是回调函数,是函数
if (!args[1]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("Second argument must be a callback function")));
}
// 强制转换成函数
Local<Function> callback = Local<Function>::Cast(args[1]);
// node.js 默认第一个参数是错误情况,第二个参数是回调函数或参与工作的参数
bool error = args[0]->BooleanValue();
if (error) {
Local<Value> err = Exception::Error(String::New("Something went wrong!"));
// 为返回的错误,创建更多的属性,数值23
err->ToObject()->Set(NODE_PSYMBOL("errno"), Integer::New(23));
// 定义回调函数的参数个数和参数数组
const unsigned argc = 1;
Local<Value> argv[argc] = { err };
// 异步回调执行 callback
callback->Call(Context::GetCurrent()->Global(), argc, argv);
} else {

// 如果执行成功,Node.js的惯例会将第一个参数设置成null
const unsigned argc = 2;
Local<Value> argv[argc] = {
Local<Value>::New(Null()),
Local<Value>::New(Integer::New(42))
};
// 异步回调执行 callback
callback->Call(Context::GetCurrent()->Global(), argc, argv);
}
return Undefined(); //异步函数,可以立即返回
}
void RegisterModule(Handle<Object> target) {
target->Set(String::NewSymbol("callback"),
FunctionTemplate::New(Callback)->GetFunction());
}
NODE_MODULE(modulename, RegisterModule);


Node.js的回调函数设计,约定俗成,callback(err,
status)
 第一参数默认是错误状态,第二个是需要回调的数值,当成功执行的时候,错误状态err设置成null。

vi binding.gyp

{
"targets": [
{
"target_name": "modulename",
"sources": [ "modulename.cpp" ]
}
]
}

vi run.js

var modulename = require(‘./build/Release/modulename‘);
modulename.callback(false, function(err, result) {
console.warn(result);
});

编译完成

#node-gyp configure
#node-gyp build
#node run.js
42

线程控制

vi modulename.cpp

#include <node.h>
#include <string>
using namespace v8;
// 声明函数
Handle<Value> Async(const Arguments& args);
void AsyncWork(uv_work_t* req);
void AsyncAfter(uv_work_t* req);
// 构建一个结构体存储异步工作的请求信息
struct Baton {

//存放回调函数,使用Persistent来声明,让系统不会在函数结束后自动回收
//当回调成功后,需要执行dispose释放空间
Persistent<Function> callback;
// 错误控制,保护错误信息和错误状态
bool error;
std::string error_message;
// 参与运行的参数
int32_t result;
};

// 函数会在Node.js当中直接被调用,它创建了一个请求的对象和等待的时间表
Handle<Value> Async(const Arguments& args) {
HandleScope scope;
if (!args[0]->IsFunction()) {
return ThrowException(Exception::TypeError(
String::New("First argument must be a callback function")));
}
// 强制将参数转换成 函数变量
Local<Function> callback = Local<Function>::Cast(args[0]);

// Baton 负责管理的异步操作的状态信息,作为参数带进去 异步回调数据的流程中
Baton* baton = new Baton();
baton->error = false;
baton->callback = Persistent<Function>::New(callback);
uv_work_t *req = new uv_work_t();
req->data = baton;

// 通过libuv进行操作的异步等待,可以通过libuv定义一个函数,当函数执行完成后回调
// 在此可以定义需要处理的异步流程AsyncWork和执行完异步流程后的操作AsyncAfter
int status = uv_queue_work(uv_default_loop(), req, AsyncWork,
(uv_after_work_cb)AsyncAfter);
assert(status == 0);
return Undefined();
}
// 注意不能使用 google v8的特性,需要用原生的C/C++来写
// 因为它是调用另外一个线程去执行任务。

void AsyncWork(uv_work_t* req) {
Baton* baton = static_cast<Baton*>(req->data);
// 执行线程池中的工作
baton->result = 42;

如果线程中出现错误,我们可以设置baton->error_message 来记录错误的字符串 和 baton-error = ture
}
//执行完任务,进行回调的时候,返回到 v8/javascript的线程
//这就意味着我们需要利用HandleScope来管理空间
void AsyncAfter(uv_work_t* req) {
HandleScope scope;
Baton* baton = static_cast<Baton*>(req->data);
if (baton->error) {
Local<Value> err = Exception::Error(String::New(baton->error_message.c_str()));
// 准备回调函数的参数
const unsigned argc = 1;
Local<Value> argv[argc] = { err };

// 用类似 try catach的方法,捕捉回调中的错误,在Node环境中
//可以利用process.on(‘uncaughtException‘)捕捉错误
TryCatch try_catch;

baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
} else {
const unsigned argc = 2;
Local<Value> argv[argc] = {
Local<Value>::New(Null()),
Local<Value>::New(Integer::New(baton->result))
};
TryCatch try_catch;
baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
}

baton->callback.Dispose();
// 处理完毕,清除对象和空间
delete baton;
delete req;
}
void RegisterModule(Handle<Object> target) {
target->Set(String::NewSymbol("async"),
FunctionTemplate::New(Async)->GetFunction());
}

NODE_MODULE(modulename, RegisterModule);


Persistent<Function>::New(callback)Local<Function>相对,Local的内存,会随着Handle
Scope的释放而释放,而Persistent则需要手动执行Dispose()方法才能释放,如果没有执行,内存则会长期备用,但同时他为回调带来了方便,即使主函数结束,执行了scope.Close后,依然能够很好地工作,uv_queue_work是libuv库的函数,libuv是Node.js异步操作的关键,关于库的描述,已经超出了本节的描述范围,详细可以参考libuv,异步的过程中,处理函数已经切出了addon模块的上下文,只能通过baton来传递状态信息,更加不能使用google
v8的函数,回调成功后,利用v8的转换函数,转换成模块需要使用的数据格式返回。

vi binding.gyp

{
"targets": [
{
"target_name": "modulename",
"sources": [ "modulename.cpp" ]
}
]
}

vi run.js

var modulename = require(‘./build/Release/modulename‘);
modulename.async(function(err, result) {
console.warn(result);
});

编译完成

#node-gyp configure
#node-gyp build
#node run.js
42

对象处理

vi moudulename.hpp

#ifndef MODULENAME_HPP
#define MODULENAME_HPP
#include <node.h>
class MyObject : public node::ObjectWrap {
public:
static v8::Persistent<v8::FunctionTemplate> constructor;
static void Init(v8::Handle<v8::Object> target);
protected:
MyObject(int val);
static v8::Handle<v8::Value> New(const v8::Arguments& args);
static v8::Handle<v8::Value> Value(const v8::Arguments& args);
int value_;
};
#endif

vi modulename.cpp

#include <node.h>
#include "modulename.hpp"
using namespace v8;
Persistent<FunctionTemplate> MyObject::constructor;
void MyObject::Init(Handle<Object> target) {
HandleScope scope;
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
Local<String> name = String::NewSymbol("MyObject");
constructor = Persistent<FunctionTemplate>::New(tpl);

// 设置对象属性的数量为1和属性名字
constructor->InstanceTemplate()->SetInternalFieldCount(1);
constructor->SetClassName(name);
// 添加属性value 和对应的数值Value
NODE_SET_PROTOTYPE_METHOD(constructor, "value", Value);
target->Set(name, constructor->GetFunction());
}
// MyObject的构造函数
MyObject::MyObject(int val)
: ObjectWrap(),
value_(val) {}
// MyObject 继承node::ObjectWrap,复写New
// 以便在node中运行 var obj = new modulename.MyObject(42)的时候,初始化MyObject
Handle<Value> MyObject::New(const Arguments& args) {
HandleScope scope;
if (!args.IsConstructCall()) {
return ThrowException(Exception::TypeError(
String::New("Use the new operator to create instances of this object."))
);
}
if (args.Length() < 1) {
return ThrowException(Exception::TypeError(
String::New("First argument must be a number")));
}
// Creates a new instance object of this type and wraps it.
MyObject* obj = new MyObject(args[0]->ToInteger()->Value());
obj->Wrap(args.This());
return args.This();
}
// 定义Value
Handle<Value> MyObject::Value(const Arguments& args) {
HandleScope scope;
// Retrieves the pointer to the wrapped object instance.
MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.This());
return scope.Close(Integer::New(obj->value_));
}
void RegisterModule(Handle<Object> target) {
MyObject::Init(target);
}
NODE_MODULE(modulename, RegisterModule);


本例子,为我们演示了,如果在模块中使用类和对象的重构,利用obj->Wrap(args.This());向模块暴漏接口,再利用ObjectWrap::Unwrap<MyObject>(args.This());解释传递对象的接口。

vi binding.gyp

{
"targets": [
{
"target_name": "modulename",
"sources": [ "modulename.cpp" ]
}
]
}

vi run.js

var modulename = require(‘./build/Release/modulename‘);
var obj = new modulename.MyObject(42);
console.warn(obj);
console.warn(obj.value());

编译完成

#node-gyp configure
#node-gyp build
#node run.js
{}
42

参考资料

https://github.com/kkaefer/node-cpp-modules
http://deadhorse.me/nodejs/2012/10/08/c_addon_in_nodejs_node_gyp.html

详解Node.js API系列C/C++ Addons(3) 程序实例,布布扣,bubuko.com

时间: 2024-10-11 15:28:52

详解Node.js API系列C/C++ Addons(3) 程序实例的相关文章

详解Node.js API系列 Crypto加密模块(1)

MD5加密算法 算法简介 MD5的全称是Message-Digest Algorithm 5(信息-摘要算法),在90年代初由Mit Laboratory for Computer Science和Rsa data security inc的Ronald l. rivest开发出来,经md2.md3和md4发展而来.它的作用是让大容量信息在用数字签名软件签署私人密匙前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的大整数).不管是md2.md4还是md5,它们都需要

node.js(API解读) - process (http://snoopyxdy.blog.163.com/blog/static/60117440201192841649337/)

node.js(API解读) - process 2011-10-28 17:05:34|  分类: node |  标签:nodejs  nodejsprocess  node.jsprocess  nodjsapi  node.jsapi   |举报 |字号 订阅 下载LOFTER 我的照片书  | nodejs的process是一个全局对象,他提供了一些方法和属性,node.js官方的API说的很简单,并没有把一些详细的应用方法和作用写出来,下面结合我自己的学习,做一下小结吧.1.Even

Node.js API

Node.js v4.4.7 Documentation(官方文档) Buffer Prior to the introduction of TypedArray in ECMAScript 2015 (ES6), the JavaScript language had no mechanism for reading or manipulating streams of binary data(二进制数据). The Buffer class was introduced as part of

Node.js教程系列~目录

Node.js这个东西在近几年火起来了,而且会一直火下去,无论在infoq还是在cnblogs,csdn上,都可以到处看到它的样子,它主推的应该就是异步式I/O 吧,是的,设计的很完美,很吸引人,虽然它与正常思维是不同的,但是当你真正认识它后,也会足够的让你爱上它!Node.js确实改变了我的编程观念,改变了我对计算机系统的认识! 名人总结的话,我们应该回味个几百次 同步式I/O: 线程在执行中如果遇到磁盘读写或网络通信(统称为I/O操作),通常要耗费较长的时间,这时操作系统会剥夺这个线程的CP

Node.js API —— About this Documentation(关于本文档)

// 说明    Node API 版本为 v0.10.31.    中文参考:http://nodeapi.ucdok.com/#/api/ 本段为博主注解. 目录 ● 关于本文档    ○ 稳定性指标    ○ JSON 输出 关于本文档 本文档的目的是既能从参考文档角度也能从概念概览角度综合地解释 Node.js API.每个小节描述了一个内建模块或较之上层的核心模块.    如若合适,属性类型.方法参数和事件监听器的参数会详细地在主标题下面列出.    每个 .html 文件都有一个与之

详解C# 网络编程系列:实现类似QQ的即时通信程序

引言: 前面专题中介绍了UDP.TCP和P2P编程,并且通过一些小的示例来让大家更好的理解它们的工作原理以及怎样.Net类库去实现它们的.为了让大家更好的理解我们平常中常见的软件QQ的工作原理,所以在本专题中将利用前面专题介绍的知识来实现一个类似QQ的聊天程序.  一.即时通信系统 在我们的生活中经常使用即时通信的软件,我们经常接触到的有:QQ.阿里旺旺.MSN等等.这些都是属于即时通信(Instant Messenger,IM)软件,IM是指所有能够即时发送和接收互联网消息的软件. 在前面专题

IOS---UICOLLECTIONVIEW详解和常用API翻译

IOS---UICOLLECTIONVIEW详解和常用API翻译 UICollectionView 1.必须要设置布局参数 2.注册cell 用法类似于UITableView 类.自动实现重用,必须注册初始化. 使用UICollectionView必须实现UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout这三个协议. Collection View的构成,我们能看到的有三个部

Node.js学习系列总索引

Node.js学习系列也积累了一些了,建个总索引方便相互交流学习,后面会持续更新^_^! 尽量写些和实战相关的,不讲太多大道理... Node.js学习笔记系列总索引 Nodejs学习笔记(一)--- 简介及安装Node.js开发环境 Nodejs学习笔记(二)--- 事件模块 Nodejs学习笔记(三)--- 模块 Nodejs学习笔记(四)--- 与MySQL交互(felixge/node-mysql) Nodejs学习笔记(五)--- Express安装入门与模版引擎ejs Nodejs学

详解PHP反射API

原文:详解PHP反射API PHP中的反射API就像Java中的java.lang.reflect包一样.它由一系列可以分析属性.方法和类的内置类组成.它在某些方面和对象函数相似,比如get_class_vars(),但是更加灵活,而且可以提供更多信息.反射API也可与PHP最新的面向对象特性一起工作,如访问控制.接口和抽象类.旧的类函数则不太容易与这些新特性一起使用.看过框架源码的朋友应该对PHP的反射机制有一定的了解,像是依赖注入,对象池,类加载,一些设计模式等等,都用到了反射机制. 1.