插件实例


以下是旨在帮助开发人员入门的一些插件示例。这些例子利用了 V8 的 API。请参阅在线 V8 参考有助于了解各种 V8 回调和解释 V8 的嵌入者指南中使用的几个概念,如处理器、作用域和函数模板等。

每个示例都使用以下的 binding.gyp 文件:

  1. {
  2. "targets": [
  3. {
  4. "target_name": "addon",
  5. "sources": ["addon.cc"]
  6. }
  7. ]
  8. }

在有一个以上的 .cc 文件的情况下,只要将额外的文件名添加到源数组。例如:

  1. "sources": ["addon.cc", "myexample.cc"]

一旦 binding.gyp 文件准备就绪,示例中的插件就可以使用 node-gyp 进行配置和构建:

  1. $ node-gyp configure build

函数的参数

插件通常会暴露可以从运行在 Node.js 中的 JavaScript 访问到的对象和函数。当函数在 JavaScript 中调用时,输入参数和返回值必须与 C/C++ 代码相互映射。

下面的例子说明了如何读取从 JavaScript 传递过来的函数参数和如何返回结果:

  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Exception;
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Number;
  9. using v8::Object;
  10. using v8::String;
  11. using v8::Value;
  12. // This is the implementation of the "add" method
  13. // Input arguments are passed using the
  14. // const FunctionCallbackInfo<Value>& args struct
  15. void Add(const FunctionCallbackInfo<Value>& args) {
  16. Isolate* isolate = args.GetIsolate();
  17. // Check the number of arguments passed.
  18. if (args.Length() < 2) {
  19. // Throw an Error that is passed back to JavaScript
  20. isolate->ThrowException(Exception::TypeError(
  21. String::NewFromUtf8(isolate, "Wrong number of arguments")));
  22. return;
  23. }
  24. // Check the argument types
  25. if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
  26. isolate->ThrowException(Exception::TypeError(
  27. String::NewFromUtf8(isolate, "Wrong arguments")));
  28. return;
  29. }
  30. // Perform the operation
  31. double value = args[0]->NumberValue() + args[1]->NumberValue();
  32. Local<Number> num = Number::New(isolate, value);
  33. // Set the return value (using the passed in
  34. // FunctionCallbackInfo<Value>&)
  35. args.GetReturnValue().Set(num);
  36. }
  37. void Init(Local<Object> exports) {
  38. NODE_SET_METHOD(exports, "add", Add);
  39. }
  40. NODE_MODULE(addon, Init)
  41. } // namespace demo

一旦编译,示例插件就可以在 Node.js 中加载和使用:

  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. console.log('This should be eight:', addon.add(3, 5));

回调

通过传递 JavaScript 函数到一个 C++ 函数并在那里执行它们,这在插件里是常见的做法。下面的例子说明了如何调用这些回调:

  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Function;
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Null;
  9. using v8::Object;
  10. using v8::String;
  11. using v8::Value;
  12. void RunCallback(const FunctionCallbackInfo<Value>& args) {
  13. Isolate* isolate = args.GetIsolate();
  14. Local<Function> cb = Local<Function>::Cast(args[0]);
  15. const unsigned argc = 1;
  16. Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };
  17. cb->Call(Null(isolate), argc, argv);
  18. }
  19. void Init(Local<Object> exports, Local<Object> module) {
  20. NODE_SET_METHOD(module, "exports", RunCallback);
  21. }
  22. NODE_MODULE(addon, Init)
  23. } // namespace demo

注意,这个例子的 Init() 使用两个参数,它接收完整的 module 对象作为第二个参数。这使得插件完全覆写 exports 以一个单一的函数代替添加函数作为 exports 的属性。

为了验证这一情况,请运行下面的 JavaScript:

  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. addon((msg) => {
  4. console.log(msg); // 'hello world'
  5. });

需要注意的是,在这个例子中,回调函数是同步调用的。

对象工厂

插件从如下示例中所示的 C++ 函数中创建和返回新对象。一个带有 msg 属性的对象被创建和返回,该属性是传递给 createObject() 的相呼应的字符串:

  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::FunctionCallbackInfo;
  5. using v8::Isolate;
  6. using v8::Local;
  7. using v8::Object;
  8. using v8::String;
  9. using v8::Value;
  10. void CreateObject(const FunctionCallbackInfo<Value>& args) {
  11. Isolate* isolate = args.GetIsolate();
  12. Local<Object> obj = Object::New(isolate);
  13. obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());
  14. args.GetReturnValue().Set(obj);
  15. }
  16. void Init(Local<Object> exports, Local<Object> module) {
  17. NODE_SET_METHOD(module, "exports", CreateObject);
  18. }
  19. NODE_MODULE(addon, Init)
  20. } // namespace demo

在 JavaScript 中测试它:

  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. var obj1 = addon('hello');
  4. var obj2 = addon('world');
  5. console.log(obj1.msg + ' ' + obj2.msg); // 'hello world'

函数工厂

另一种常见情况是创建 JavaScript 函数来包装 C++ 函数,并返回那些使其回到 JavaScript:

  1. // addon.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::Function;
  5. using v8::FunctionCallbackInfo;
  6. using v8::FunctionTemplate;
  7. using v8::Isolate;
  8. using v8::Local;
  9. using v8::Object;
  10. using v8::String;
  11. using v8::Value;
  12. void MyFunction(const FunctionCallbackInfo<Value>& args) {
  13. Isolate* isolate = args.GetIsolate();
  14. args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));
  15. }
  16. void CreateFunction(const FunctionCallbackInfo<Value>& args) {
  17. Isolate* isolate = args.GetIsolate();
  18. Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
  19. Local<Function> fn = tpl->GetFunction();
  20. // omit this to make it anonymous
  21. fn->SetName(String::NewFromUtf8(isolate, "theFunction"));
  22. args.GetReturnValue().Set(fn);
  23. }
  24. void Init(Local<Object> exports, Local<Object> module) {
  25. NODE_SET_METHOD(module, "exports", CreateFunction);
  26. }
  27. NODE_MODULE(addon, Init)
  28. } // namespace demo

去测试:

  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. var fn = addon();
  4. console.log(fn()); // 'hello world'

包装 C++ 对象

另外,也可以包装 C++ 对象/类允许以使用 JavaScript 的 new 操作的方式来创建新的实例:

  1. // addon.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using v8::Local;
  6. using v8::Object;
  7. void InitAll(Local<Object> exports) {
  8. MyObject::Init(exports);
  9. }
  10. NODE_MODULE(addon, InitAll)
  11. } // namespace demo

那么,在 myobject.h 中,包装类继承自 node::ObjectWrap

  1. // myobject.h
  2. #ifndef MYOBJECT_H
  3. #define MYOBJECT_H
  4. #include <node.h>
  5. #include <node_object_wrap.h>
  6. namespace demo {
  7. class MyObject : public node::ObjectWrap {
  8. public:
  9. static void Init(v8::Local<v8::Object> exports);
  10. private:
  11. explicit MyObject(double value = 0);
  12. ~MyObject();
  13. static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  14. static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  15. static v8::Persistent<v8::Function> constructor;
  16. double value_;
  17. };
  18. } // namespace demo
  19. #endif

myobject.cc 中,实现要被暴露的各种方法。下面,plusOne() 方法是通过将其添加到构造函数的原型来暴露的:

  1. // myobject.cc
  2. #include "myobject.h"
  3. namespace demo {
  4. using v8::Function;
  5. using v8::FunctionCallbackInfo;
  6. using v8::FunctionTemplate;
  7. using v8::Isolate;
  8. using v8::Local;
  9. using v8::Number;
  10. using v8::Object;
  11. using v8::Persistent;
  12. using v8::String;
  13. using v8::Value;
  14. Persistent<Function> MyObject::constructor;
  15. MyObject::MyObject(double value) : value_(value) {
  16. }
  17. MyObject::~MyObject() {
  18. }
  19. void MyObject::Init(Local<Object> exports) {
  20. Isolate* isolate = exports->GetIsolate();
  21. // Prepare constructor template
  22. Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  23. tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
  24. tpl->InstanceTemplate()->SetInternalFieldCount(1);
  25. // Prototype
  26. NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
  27. constructor.Reset(isolate, tpl->GetFunction());
  28. exports->Set(String::NewFromUtf8(isolate, "MyObject"),
  29. tpl->GetFunction());
  30. }
  31. void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  32. Isolate* isolate = args.GetIsolate();
  33. if (args.IsConstructCall()) {
  34. // Invoked as constructor: `new MyObject(...)`
  35. double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
  36. MyObject* obj = new MyObject(value);
  37. obj->Wrap(args.This());
  38. args.GetReturnValue().Set(args.This());
  39. } else {
  40. // Invoked as plain function `MyObject(...)`, turn into construct call.
  41. const int argc = 1;
  42. Local<Value> argv[argc] = { args[0] };
  43. Local<Function> cons = Local<Function>::New(isolate, constructor);
  44. args.GetReturnValue().Set(cons->NewInstance(argc, argv));
  45. }
  46. }
  47. void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  48. Isolate* isolate = args.GetIsolate();
  49. MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  50. obj->value_ += 1;
  51. args.GetReturnValue().Set(Number::New(isolate, obj->value_));
  52. }
  53. } // namespace demo

要构建这个例子,myobject.cc 文件必须被添加到 binding.gyp

  1. {
  2. "targets": [
  3. {
  4. "target_name": "addon",
  5. "sources": [
  6. "addon.cc",
  7. "myobject.cc"
  8. ]
  9. }
  10. ]
  11. }

测试:

  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. var obj = new addon.MyObject(10);
  4. console.log(obj.plusOne()); // 11
  5. console.log(obj.plusOne()); // 12
  6. console.log(obj.plusOne()); // 13

包装对象的工厂

另外,它可以使用一个工厂模式,以避免显式地使用 JavaScript 的 new 操作来创建对象的实例:

  1. var obj = addon.createObject();
  2. // instead of:
  3. // var obj = new addon.Object();

首先,在 addon.cc 中实现 createObject() 方法:

  1. // addon.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using v8::FunctionCallbackInfo;
  6. using v8::Isolate;
  7. using v8::Local;
  8. using v8::Object;
  9. using v8::String;
  10. using v8::Value;
  11. void CreateObject(const FunctionCallbackInfo<Value>& args) {
  12. MyObject::NewInstance(args);
  13. }
  14. void InitAll(Local<Object> exports, Local<Object> module) {
  15. MyObject::Init(exports->GetIsolate());
  16. NODE_SET_METHOD(module, "exports", CreateObject);
  17. }
  18. NODE_MODULE(addon, InitAll)
  19. } // namespace demo

myobject.h 中,添加静态方法 NewInstance() 来处理实例化对象。这种方法需要在 JavaScript 中使用 new

  1. // myobject.h
  2. #ifndef MYOBJECT_H
  3. #define MYOBJECT_H
  4. #include <node.h>
  5. #include <node_object_wrap.h>
  6. namespace demo {
  7. class MyObject : public node::ObjectWrap {
  8. public:
  9. static void Init(v8::Isolate* isolate);
  10. static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
  11. private:
  12. explicit MyObject(double value = 0);
  13. ~MyObject();
  14. static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  15. static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  16. static v8::Persistent<v8::Function> constructor;
  17. double value_;
  18. };
  19. } // namespace demo
  20. #endif

myobject.cc 中的实现类似与之前的例子:

  1. // myobject.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using v8::Function;
  6. using v8::FunctionCallbackInfo;
  7. using v8::FunctionTemplate;
  8. using v8::Isolate;
  9. using v8::Local;
  10. using v8::Number;
  11. using v8::Object;
  12. using v8::Persistent;
  13. using v8::String;
  14. using v8::Value;
  15. Persistent<Function> MyObject::constructor;
  16. MyObject::MyObject(double value) : value_(value) {
  17. }
  18. MyObject::~MyObject() {
  19. }
  20. void MyObject::Init(Isolate* isolate) {
  21. // Prepare constructor template
  22. Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  23. tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
  24. tpl->InstanceTemplate()->SetInternalFieldCount(1);
  25. // Prototype
  26. NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);
  27. constructor.Reset(isolate, tpl->GetFunction());
  28. }
  29. void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  30. Isolate* isolate = args.GetIsolate();
  31. if (args.IsConstructCall()) {
  32. // Invoked as constructor: `new MyObject(...)`
  33. double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
  34. MyObject* obj = new MyObject(value);
  35. obj->Wrap(args.This());
  36. args.GetReturnValue().Set(args.This());
  37. } else {
  38. // Invoked as plain function `MyObject(...)`, turn into construct call.
  39. const int argc = 1;
  40. Local<Value> argv[argc] = { args[0] };
  41. Local<Function> cons = Local<Function>::New(isolate, constructor);
  42. args.GetReturnValue().Set(cons->NewInstance(argc, argv));
  43. }
  44. }
  45. void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  46. Isolate* isolate = args.GetIsolate();
  47. const unsigned argc = 1;
  48. Local<Value> argv[argc] = { args[0] };
  49. Local<Function> cons = Local<Function>::New(isolate, constructor);
  50. Local<Object> instance = cons->NewInstance(argc, argv);
  51. args.GetReturnValue().Set(instance);
  52. }
  53. void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  54. Isolate* isolate = args.GetIsolate();
  55. MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  56. obj->value_ += 1;
  57. args.GetReturnValue().Set(Number::New(isolate, obj->value_));
  58. }
  59. } // namespace demo

再来一次,建立这个例子,myobject.cc 文件必须被添加到 binding.gyp

  1. {
  2. "targets": [
  3. {
  4. "target_name": "addon",
  5. "sources": [
  6. "addon.cc",
  7. "myobject.cc"
  8. ]
  9. }
  10. ]
  11. }

测试它:

  1. // test.js
  2. const createObject = require('./build/Release/addon');
  3. var obj = createObject(10);
  4. console.log(obj.plusOne()); // 11
  5. console.log(obj.plusOne()); // 12
  6. console.log(obj.plusOne()); // 13
  7. var obj2 = createObject(20);
  8. console.log(obj2.plusOne()); // 21
  9. console.log(obj2.plusOne()); // 22
  10. console.log(obj2.plusOne()); // 23

传递包装的对象

除了包装和返回的 C++ 对象,也有可能是通过 Node.js 的辅助函数 node::ObjectWrap::Unwrap 展开传过来的包装对象。下面的例子显示了一个 add() 函数可以采用两个 MyObject 对象作为输入参数:

  1. // addon.cc
  2. #include <node.h>
  3. #include <node_object_wrap.h>
  4. #include "myobject.h"
  5. namespace demo {
  6. using v8::FunctionCallbackInfo;
  7. using v8::Isolate;
  8. using v8::Local;
  9. using v8::Number;
  10. using v8::Object;
  11. using v8::String;
  12. using v8::Value;
  13. void CreateObject(const FunctionCallbackInfo<Value>& args) {
  14. MyObject::NewInstance(args);
  15. }
  16. void Add(const FunctionCallbackInfo<Value>& args) {
  17. Isolate* isolate = args.GetIsolate();
  18. MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
  19. args[0]->ToObject());
  20. MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
  21. args[1]->ToObject());
  22. double sum = obj1->value() + obj2->value();
  23. args.GetReturnValue().Set(Number::New(isolate, sum));
  24. }
  25. void InitAll(Local<Object> exports) {
  26. MyObject::Init(exports->GetIsolate());
  27. NODE_SET_METHOD(exports, "createObject", CreateObject);
  28. NODE_SET_METHOD(exports, "add", Add);
  29. }
  30. NODE_MODULE(addon, InitAll)
  31. } // namespace demo

myobject.h 中,添加了一个公共方法以允许在展开对象后访问私有值。

  1. // myobject.h
  2. #ifndef MYOBJECT_H
  3. #define MYOBJECT_H
  4. #include <node.h>
  5. #include <node_object_wrap.h>
  6. namespace demo {
  7. class MyObject : public node::ObjectWrap {
  8. public:
  9. static void Init(v8::Isolate* isolate);
  10. static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
  11. inline double value() const { return value_; }
  12. private:
  13. explicit MyObject(double value = 0);
  14. ~MyObject();
  15. static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  16. static v8::Persistent<v8::Function> constructor;
  17. double value_;
  18. };
  19. } // namespace demo
  20. #endif

myobject.cc 中的实现类似与之前的例子:

  1. // myobject.cc
  2. #include <node.h>
  3. #include "myobject.h"
  4. namespace demo {
  5. using v8::Function;
  6. using v8::FunctionCallbackInfo;
  7. using v8::FunctionTemplate;
  8. using v8::Isolate;
  9. using v8::Local;
  10. using v8::Object;
  11. using v8::Persistent;
  12. using v8::String;
  13. using v8::Value;
  14. Persistent<Function> MyObject::constructor;
  15. MyObject::MyObject(double value) : value_(value) {
  16. }
  17. MyObject::~MyObject() {
  18. }
  19. void MyObject::Init(Isolate* isolate) {
  20. // Prepare constructor template
  21. Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  22. tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
  23. tpl->InstanceTemplate()->SetInternalFieldCount(1);
  24. constructor.Reset(isolate, tpl->GetFunction());
  25. }
  26. void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  27. Isolate* isolate = args.GetIsolate();
  28. if (args.IsConstructCall()) {
  29. // Invoked as constructor: `new MyObject(...)`
  30. double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
  31. MyObject* obj = new MyObject(value);
  32. obj->Wrap(args.This());
  33. args.GetReturnValue().Set(args.This());
  34. } else {
  35. // Invoked as plain function `MyObject(...)`, turn into construct call.
  36. const int argc = 1;
  37. Local<Value> argv[argc] = { args[0] };
  38. Local<Function> cons = Local<Function>::New(isolate, constructor);
  39. args.GetReturnValue().Set(cons->NewInstance(argc, argv));
  40. }
  41. }
  42. void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  43. Isolate* isolate = args.GetIsolate();
  44. const unsigned argc = 1;
  45. Local<Value> argv[argc] = { args[0] };
  46. Local<Function> cons = Local<Function>::New(isolate, constructor);
  47. Local<Object> instance = cons->NewInstance(argc, argv);
  48. args.GetReturnValue().Set(instance);
  49. }
  50. } // namespace demo

测试:

  1. // test.js
  2. const addon = require('./build/Release/addon');
  3. var obj1 = addon.createObject(10);
  4. var obj2 = addon.createObject(20);
  5. var result = addon.add(obj1, obj2);
  6. console.log(result); // 30

AtExit 挂钩

“AtExit” 挂钩是一个函数,它是在 Node.js 事件循环结束后,但在 JavaScript 虚拟机被终止或 Node.js 关闭前调用。“AtExit” 挂钩使用 node::AtExit API 注册。

void AtExit(callback, args)

  • callbackvoid (*)(void*) - 一个退出时调用的函数的指针。

  • argsvoid* - 一个退出时传递给回调的指针。

注册在事件循环结束后并在虚拟机被终止前运行的退出挂钩。

AtExit 有两个参数:指向一个在退出运行的回调函数,和要传递给该回调的无类型的上下文数据的指针。

回调按照后进先出的顺序执行。

下面的 addon.cc 实现了 AtExit:

  1. // addon.cc
  2. #undef NDEBUG
  3. #include <assert.h>
  4. #include <stdlib.h>
  5. #include <node.h>
  6. namespace demo {
  7. using node::AtExit;
  8. using v8::HandleScope;
  9. using v8::Isolate;
  10. using v8::Local;
  11. using v8::Object;
  12. static char cookie[] = "yum yum";
  13. static int at_exit_cb1_called = 0;
  14. static int at_exit_cb2_called = 0;
  15. static void at_exit_cb1(void* arg) {
  16. Isolate* isolate = static_cast<Isolate*>(arg);
  17. HandleScope scope(isolate);
  18. Local<Object> obj = Object::New(isolate);
  19. assert(!obj.IsEmpty()); // assert VM is still alive
  20. assert(obj->IsObject());
  21. at_exit_cb1_called++;
  22. }
  23. static void at_exit_cb2(void* arg) {
  24. assert(arg == static_cast<void*>(cookie));
  25. at_exit_cb2_called++;
  26. }
  27. static void sanity_check(void*) {
  28. assert(at_exit_cb1_called == 1);
  29. assert(at_exit_cb2_called == 2);
  30. }
  31. void init(Local<Object> exports) {
  32. AtExit(sanity_check);
  33. AtExit(at_exit_cb2, cookie);
  34. AtExit(at_exit_cb2, cookie);
  35. AtExit(at_exit_cb1, exports->GetIsolate());
  36. }
  37. NODE_MODULE(addon, init);
  38. } // namespace demo

在 JavaScript 中运行测试:

  1. // test.js
  2. const addon = require('./build/Release/addon');