🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# Addons插件 Addons插件就是动态连接库。它类似胶水,将c、c++和Node粘贴起来。它的API(目前来说)相当复杂,涉及到了几个类库的知识。 - V8 JavaScript引擎,一个 C++ 类库. 用于和JavaScript进行交互的接口。 创建对象, 调用函数等. 文档大部分在这里: `v8.h` 头文件 (`deps/v8/include/v8.h`在Node源代码目录里), 也有可用的线上文档 [线上](http://izs.me/v8-docs/main.html). (译者:想要学习c++的addons插件编写,必须先了解v8的接口) - [libuv](https://github.com/joyent/libuv), C语言编写的事件循环类库。任何时候需要等待一个文件描述符变为可读状态,等待一个定时器,或者等待一个接受信号都需要使用libuv类库的接口。也就是说,如果你执行任何I/O操作,libuv类库将会被用到。 - 内部 Node 类库.最重要的接口就是 `node::ObjectWrap` 类,这个类你应该是最可能想要派生的。 - 其他.请参阅 `deps/` 获得更多可用类库。 Node 静态编译了所有依赖到它的可执行文件中去了。当编译你的模块时,你不必担心无法连接上述那些类库。 (译者:换而言之,你在编译自己的addons插件时,只管在头部 #include <uv.h>,不必在binding.gyp中声明) 下面所有的例子都可以下载到: [下载](https://github.com/rvagg/node-addon-examples) 这或许能成为你学习和创作自己addon插件的起点。 ### Hello world(世界你好) 作为开始,让我们用编写一个小的addon插件,这个addon插件的c++代码相当于下面的JavaScript代码。 ~~~ module.exports.hello = function() { return 'world'; }; ~~~ 首先我们创建一个 `hello.cc`文件: ~~~ NODE_MODULE(hello, init)//译者:将addon插件名hello和上述init函数关联输出 ~~~ 注意所有Node的addons插件都必须输出一个初始化函数: ~~~ void Initialize (Handle<Object> exports); NODE_MODULE(module_name, Initialize) ~~~ 在`NODE_MODULE`之后没有分号,因为它不是一个函数(请参阅`node.h`) 这个`module_name`(模块名)需要和最后编译生成的2进制文件名(减去.node后缀名)相同。 源代码需要生成在`hello.node`,这个2进制addon插件中。 需要做到这些,我们要创建一个名为`binding.gyp`的文件,它描述了创建这个模块的配置,并且它的格式是类似JSON的。 文件将被命令:[node-gyp](https://github.com/TooTallNate/node-gyp) 编译。 ~~~ { "targets": [ { "target_name": "hello", //译者:addon插件名,注意这里的名字必需和上面NODE_MODULE中的一致 "sources": [ "hello.cc" ] //译者:这是需要编译的源文件 } ] } ~~~ 下一步是根据当前的操作系统平台,利用`node-gyp configure`命令,生成合适的项目文件。 现在你会有一个`Makefile` (在Unix平台) 或者一个 `vcxproj` file (在Windows上),它们都在`build/` 文件夹中. 然后执行命令 `node-gyp build`进行编译。 (译者:当然你可以执行 `node-gyp rebuild`一步搞定) 现在你已经有了编译好的 `.node` 文件了,这个编译好的绑定文件会在目录 `build/Release/`下 现在你可以使用这个2进制addon插件在Node项目`hello.js` 中了,通过指明`require`这个刚刚创建的`hello.node`模块使用它。 ~~~ console.log(addon.hello()); // 'world' ~~~ 请阅读下面的内容获得更多详情或者访问[https://github.com/arturadib/node-qt](https://github.com/arturadib/node-qt)获取一个生产环境的例子。 ### Addon patterns(插件方式) 下面是一些帮助你开始编写addon插件的方式。参考这个在线的[v8 手册](http://izs.me/v8-docs/main.html)用来帮助你调用各种v8接口, 然后是v8的 [嵌入式开发向导](http://code.google.com/apis/v8/embed.html) ,解释几个概念,如 handles, scopes,function templates等。 为了能跑起来这些例子,你必须用 `node-gyp` 来编译他们。 创建一个`binding.gyp` 文件: ~~~ { "targets": [ { "target_name": "addon", "sources": [ "addon.cc" ] } ] } ~~~ 事实上可以有多个 `.cc` 文件, 就简单的在 `sources` 数组里加上即可,例子: ~~~ "sources": ["addon.cc", "myexample.cc"] ~~~ 现在你有了你的`binding.gyp`文件了,你可要开始执行configure 和 build 命令构建你的addon插件了 ~~~ $ node-gyp configure build ~~~ ### Function arguments(函数参数) 下面的部分说明了如何从JavaScript的函数调用获得参数然后返回一个值。这是主要的内容并且仅需要源代码`addon.cc`。 ~~~ NODE_MODULE(addon, Init) ~~~ 你可以使用下面的JavaScript代码片段来测试它 ~~~ console.log( 'This should be eight:', addon.add(3,5) ); ~~~ ### Callbacks(回调) 你可以传递JavaScript functions 到一个C++ function 并且执行他们,这里是 `addon.cc`文件: ~~~ NODE_MODULE(addon, Init) ~~~ 注意这个例子对`Init()`使用了两个参数,将完整的 `module` 对象作为第二个参数传入。这允许addon插件完全的重写 `exports`,这样就可以用一个函数代替多个函数作为`exports`的属性了。 你可以使用下面的JavaScript代码片段来测试它 ~~~ addon(function(msg){ console.log(msg); // 'hello world' }); ~~~ ### Object factory(对象工厂) 在这个`addon.cc`文件里用一个c++函数,你可以创建并且返回一个新的对象,这个新的对象拥有一个msg的属性,它的值是通过createObject()方法传入的 ~~~ NODE_MODULE(addon, Init) ~~~ 在js中测试如下: ~~~ var obj1 = addon('hello'); var obj2 = addon('world'); console.log(obj1.msg+' '+obj2.msg); // 'hello world' ~~~ ### Function factory(函数工厂) 这次将展示如何创建并返回一个JavaScript function函数,这个函数其实是通过c++包装的。 ~~~ NODE_MODULE(addon, Init) ~~~ 测试它: ~~~ var fn = addon(); console.log(fn()); // 'hello world' ~~~ ### Wrapping C++ objects(包装c++对象) 这里将创建一个被c++包裹的对象或类`MyObject`,它是可以在JavaScript中通过`new`操作符实例化的。 首先我们要准备主要的模块文件`addon.cc`: ~~~ NODE_MODULE(addon, InitAll) ~~~ 然后在`myobject.h`文件中创建你的包装类,它继承自 `node::ObjectWrap`: ~~~ #endif ~~~ 在文件 `myobject.cc` 可以实施各种你想要暴露给js的方法。 这里我们暴露方法名为 `plusOne`给就是,它表示将构造函数的属性加1. ~~~ return scope.Close(Number::New(obj->counter_)); } ~~~ 测试它: ~~~ var obj = new addon.MyObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 ~~~ ### Factory of wrapped objects(工厂包装对象) 这是非常有用的,当你想创建原生的JavaScript对象时,又不想明确的使用JavaScript的`new`操作符。 ~~~ var obj = addon.createObject(); // 用上面的方式代替下面的: // var obj = new addon.Object(); ~~~ 让我们注册在 `addon.cc` 文件中注册`createObject`方法: ~~~ NODE_MODULE(addon, InitAll) ~~~ 在`myobject.h`文件中,我们现在介绍静态方法NewInstance`,它能够实例化对象(举个例子,它的工作就像是 在JavaScript中的`new` 操作符。) ~~~ #endif ~~~ 这里的处理方式和上面的 `myobject.cc`很像: ~~~ return scope.Close(Number::New(obj->counter_)); } ~~~ 测试它: ~~~ var obj2 = createObject(20); console.log( obj2.plusOne() ); // 21 console.log( obj2.plusOne() ); // 22 console.log( obj2.plusOne() ); // 23 ~~~ ### Passing wrapped objects around(传递包装的对象) 除了包装和返回c++对象以外,你可以传递他们并且通过Node的`node::ObjectWrap::Unwrap`帮助函数解包装他们。 在下面的`addon.cc` 文件中,我们介绍了一个函数`add()`,它能够获取2个`MyObject`对象。 ~~~ NODE_MODULE(addon, InitAll) ~~~ 为了使事情变得有趣,我们在 `myobject.h` 采用一个公共的方法,所以我们能够在unwrapping解包装对象之后使用私有成员的值。 ~~~ #endif ~~~ `myobject.cc`文件的处理方式和前面类似 ~~~ return scope.Close(instance); } ~~~ 测试它: ~~~ var obj1 = addon.createObject(10); var obj2 = addon.createObject(20); var result = addon.add(obj1, obj2); ~~~ ~~~ console.log(result); // 30 ~~~