详解Node.js API系列C/C++ Addons(3) 程序实例
2013-09-12 17:54
916 查看
再续前文,前文介绍了node.js 的addon用法和google v8 引擎,下面,我们进入真正的编码,下面将会通过六个例子,学习node addon 范例,了解addon编程的特性
http://blog.whattoc.com/2013/09/08/nodejs_api_addon_3/
创建一个空项目
随机数模块
向模块传递参数
回调函数处理
线程处理
对象管理
vi modulename.cpp
vi binding.gyp
vi run.js
编译addon模块,编译器前,需要确保安装好
vi modulename.cpp
HandleScope scope,申请一个装载Handle(Local)的容器,当生命周期结束时,即scope.Close()的时候,Handle的内容就会被释放。
String, Number, Boolean, Object, Array 继承了Value,想了解更多关于数据类型的转换可以参考《Node.js
C++ addon编写实战(二)之对象转换》
vi binding.gyp
vi run.js
编译执行
vi modulename.cpp
vi binding.gyp
参数传入的情况,少不参数的合法性校验和类型转换,总结一下几种转换和类型合法性的检测
vi run.js
编译执行
vi modulename.cpp
Node.js的回调函数设计,约定俗成,
vi binding.gyp
vi run.js
编译完成
vi modulename.cpp
与
Scope的释放而释放,而Persistent则需要手动执行Dispose()方法才能释放,如果没有执行,内存则会长期备用,但同时他为回调带来了方便,即使主函数结束,执行了scope.Close后,依然能够很好地工作,
v8的函数,回调成功后,利用v8的转换函数,转换成模块需要使用的数据格式返回。
vi binding.gyp
vi run.js
编译完成
vi moudulename.hpp
vi modulename.cpp
本例子,为我们演示了,如果在模块中使用类和对象的重构,利用
vi binding.gyp
vi run.js
编译完成
https://github.com/kkaefer/node-cpp-modules
http://deadhorse.me/nodejs/2012/10/08/c_addon_in_nodejs_node_gyp.html
http://blog.whattoc.com/2013/09/08/nodejs_api_addon_3/
创建一个空项目
随机数模块
向模块传递参数
回调函数处理
线程处理
对象管理
创建一个空项目
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-moduleshttp://deadhorse.me/nodejs/2012/10/08/c_addon_in_nodejs_node_gyp.html
相关文章推荐
- 详解Node.js API系列C/C++ Addons(3) 程序实例
- 详解Node.js API系列C/C++ Addons(4) Javascript也能搞嵌入式?
- 详解Node.js API系列C/C++ Addons(1) API文档
- 详解Node.js API系列C/C++ Addons(2) Google V8引擎
- 详解Node.js API系列 Module模块(2) 案例分析
- 详解Node.js API系列 Crypto加密模块(1)
- 详解Node.js API系列 Crypto加密模块(2) Hmac
- 详解Node.js API系列 Http模块(2) CNodejs爬虫实现
- 详解Node.js API系列 Crypto加密模块
- 详解Node.js API系列 Http模块(1) 构造一个简单的静态页服务器
- 微信小程序使用第三方库Immutable.js实例详解
- Node.js Addons翻译(C/C++扩展)
- nw.js node-webkit系列(9)Native UI API App的使用
- nw.js node-webkit系列(12)Native UI API File dialogs的使用
- node.js的API中文文档详解
- nw.js node-webkit系列(4)Native UI API 概述和应用规范
- node.js系列(实例):原生node.js实现接收前台post请求提交数据
- node.js操作MongoDB的实例详解
- 微信小程序 wx.request(object) API详解及实例代码
- 微信小程序本作用域下调用全局JS详解及实例