当前位置: 首页 > 科技观察

开发指南:如何写一个Node.js插件

时间:2023-03-17 13:38:08 科技观察

Node.js用JavaScript写后台非常有效,值得多尝试。但是,如果你需要一些不能直接使用的功能,甚至是根本无法实现的模块,你能从C/C++库中引入这样的结果吗?答案是肯定的,你所要做的就是编写一个插件,并通过它在你自己的JavaScript代码中使用来自其他代码库的资源。让我们一起开启今天的探究之旅吧。__________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________这意味着我们可以从C/C++库中获取任何内容,并通过创建插件将其包含在Node.js中。例如,我们将为标准std::string对象创建一组包装器。准备工作在我们开始编写之前,您首先需要确保您已经准备好后续模块编译所需的所有材料。您需要node-gyp及其所有依赖项。可以使用以下命令安装node-gyp:npminstall-gnode-gyp在依赖方面,我们需要为Unix系统准备以下项目:?Python(需要2.7版本,3.x无法正常运行)?make?C++编译器工具链(如gpp或g++)例如,在Ubuntu上,您可以使用以下命令安装上述所有项目(Python2.7应该已预安装):sudoapt-getinstallbuild-essentials要求是:?Python(2.7.3、3.x版本无法正常运行)?MicrosoftVisualStudioC++2010(适用于WindowsXP/Vista)?MicrosoftVisualStudioC++2012forWindowsDesktop(适用于Windows7/8)强调Express版本VisualStudio也可以正常工作。_________________________________________binding.gyp文件node-gyp使用此文件为我们的插件生成正确的构建文件。你可以点击这里查看维基百科提供的.gyp文件文档,但是我们今天要用的例子非常简单,所以只需要使用下面的代码:{"targets":[{"target_name":"stdstring","sources":["addon.cc","stdstring.cc"]}]}其中target_name可以设置为任何你喜欢的。sources数组包含插件需要使用的所有源文件。在我们的示例中,我们还包括addon.cc,其中包含编译插件和stdstring.cc所需的代码,以及我们的包装类。_____________________________________STDStringWrapper类第一步,我们要做的是在stdstring.h文件中定义我们自己的类。如果熟悉C++编程,对下面两行代码一定不会陌生。#ifndefSTDSTRING_H#defineSTDSTRING_H这是一个标准的包含保护。接下来,我们需要将以下两个标头包含到include类别中:#include#include第一个是针对std::string类的,而第二个include是针对所有Node和V8相关内容的。这一步完成后,我们就可以声明我们自己的类了:classSTDStringWrapper:publicnode::ObjectWrap{对于我们打算包含在插件中的所有类,我们都必须扩展node::ObjectWrap类。现在我们可以开始定义这个类的私有属性了:private:std::string*s_;explicitSTDStringWrapper(std::strings="");~STDStringWrapper();除了构造函数和分析函数,我们还需要提供std::string定义的指针。这是该技术的核心,可用于将C/C++代码库与Node进行接口——我们为这个C/C++类定义了一个私有指针,我们将使用这个指针来实现所有后续方法中的操作。现在我们声明constructorstatic属性,它将为我们在V8中创建的类提供函数:staticv8::Persistentconstructor;感兴趣的朋友可以点此参考模板描述方案了解更多详情。现在我们还需要一个New方法,它将分配给上述构造函数,V8将初始化我们的类:staticv8::HandleNew(constv8::Arguments&args);作用于V8的每个函数都应遵循以下要求:它将接受对v8::Arguments对象的引用并返回v8::Handle>v8::Value>-这正是我们在编写代码时V8处理弱类型JavaScript的方式强类型C++。之后,我们需要在对象的原型中插入另外两个方法:staticv8::Handleadd(constv8::Arguments&args);staticv8::HandletoString(constv8::Arguments&args);当与普通JavaScript字符串一起使用时,toString()方法允许我们获取s_的值而不是[Objectobject]的值。最后介绍初始化方法(该方法会被V8调用并赋值给构造函数)并关闭includeguard:public:staticvoidInit(v8::Handleexports);};#endifexports对象在JavaScript模块中的作用相当于模块.exports。#p#stdstring.cc文件、构造函数和分析函数现在创建stdstring.cc文件。我们首先需要包含我们的头文件:#include"stdstring.h"下面定义了构造函数的属性(因为它是一个静态函数):v8::PersistentSTDStringWrapper::constructor;该类的构造函数将分配s_属性:STDStringWrapper::STDStringWrapper(std::strings){s_=newstd::string(s);并且解析函数将删除它以避免内存溢出:STDStringWrapper::~STDStringWrapper(){deletes_;}再次强调,大家一定要删除所有用new分配的内容,因为每一次这样的情况都可能引发异常,所以请牢记以上操作或者使用共享指针。______________________________________Init方法V8会调用这个方法来初始化我们的类(分配构造函数,将我们打算在JavaScript中使用的所有内容放在exports对象中):voidSTDStringWrapper::Init(v8::Handleexports){首先,我们需要为我们的New方法创建一个函数模板:v8::Localtpl=v8::FunctionTemplate::New(New);这有点类似于JavaScript中的新函数——它允许我们准备自己的JavaScript类。现在我们可以根据实际需要给函数起名字了(如果错过这一步,构造函数会处于匿名状态,即名字是functionsomeName(){}或function(){}):tpl->SetClassName(v8::String::NewSymbol("STDString"));我们使用v8::String::NewSymbol()为属性名称创建一种特殊类型的字符串——这为引擎节省了一点工作时间。之后,我们需要设置我们的类实例包含多少字段:tpl->InstanceTemplate()->SetInternalFieldCount(2);我们有两个方法-add()和toString(),所以我们将Set计数为2。现在我们可以将我们自己的方法添加到函数原型中:tpl->PrototypeTemplate()->Set(v8::String::NewSymbol("add"),v8::FunctionTemplate::New(add)->GetFunction());tpl->PrototypeTemplate()->Set(v8::String::NewSymbol("toString"),v8::FunctionTemplate::New(toString)->GetFunction());这部分代码看起来比较大,但是只要仔细观察就会发现规律:我们使用tpl->PrototypeTemplate()->Set()来添加各个方法。我们还使用v8::String::NewSymbol()为它们提供名称和函数模板。最后,我们可以将构造函数放在构造函数类属性中的导出对象中:constructor=v8::Persistent::New(tpl->GetFunction());exports->Set(v8::String::NewSymbol("STDString"),构造函数);}______________________________________________新方法现在我们要做的是定义一个与JavaScript对象一样工作的方法。需要为其创建作用域:v8::HandleScopescope;之后,我们可以使用args对象的.IsConstructCall()方法来检查是否可以使用new关键字调用构造函数:if(args.IsConstructCall()){如果可以,我们先将参数传递给std::字符串如下:v8::String::Utf8Valuestr(args[0]->ToString());std::strings(*str);...所以我们可以将它传递给包装类的构造函数:STDStringWrapper*obj=newSTDStringWrapper(s);之后,我们可以使用之前创建的对象的.Wrap()方法(继承自node::ObjectWrap)来将其赋值给这个变量:obj->Wrap(args.This());最后,我们可以返回这个新创建的对象:returnargs.This();如果函数不能用new调用,我们也可以直接调用构造函数。接下来,我们要做的是为参数计数设置一个常量:}else{constintargc=1;现在我们需要用我们的参数创建一个数组:v8::Localargv[argc]={args[0]};constructor->NewInstance方法的结果被传递给scope.Close以便稍后可以使用该对象(scope.Close基本上允许您通过将对象移至更高的范围来维护对象句柄-这也是函数的工作原理):returnscope.Close(constructor->NewInstance(argc,argv));添加方法现在让我们创建添加方法,它允许您向对象的内部std::string添加内容:v8::HandleSTDStringWrapper::add(constv8::Arguments&args){首先,我们需要创建一个我们函数的作用域,并像以前一样将参数转换为std::string:v8::HandleScopescope;v8::String::Utf8Valuestr(args[0]->ToString());std::strings(*str);现在我们需要解压这个对象。我们之前已经完成了这个展开操作——这次我们从this变量中获得了一个指向对象的指针。STDStringWrapper*obj=ObjectWrap::Unwrap(args.This());然后我们可以访问s_属性并使用它的.append()方法:obj->s_->append(s);最后,我们返回s_属性的当前值(需要再次使用scope.Close):returnscope.Close(v8::String::New(obj->s_->c_str()));由于v8::String::New()方法只能接受一个char指针作为值,所以我们需要使用obj->s_->c_str()来获取。#p#toString方法最后使用的方法允许我们将对象转换为JavaScriptString:v8::HandleSTDStringWrapper::toString(constv8::Arguments&args){它与我们之前提到的方法类似,您还需要创建范围:v8::HandleScopescope;解压对象:STDStringWrapper*obj=ObjectWrap::Unwrap(args.This());接下来,将s_属性作为v8::String返回:returnscope.Close(v8::String::New(obj->s_->c_str()));________________________________________________________________________________________________________________________________________________________________________________________________________________________________这部分工作只需要两行命令。首先:node-gypconfigure这将根据您的操作系统和处理器类型(UNIX上的Makefile,Windows上的vcxproj)创建合适的构建配置方案。要编译和链接库,只需调用:node-gypbuild如果一切顺利,您应该在控制台上看到以下内容:您还应该在插件文件夹中创建一个构建目录。测试现在我们可以测试我们的插件了。在我们的插件目录下创建一个test.js文件和必要的编译库(可以跳过.node扩展名):varaddon=require('./build/Release/addon');接下来,为我们的对象创建一个新的实例:vartest=newaddon.STDString('test');然后对其进行操作,比如添加或者转换成字符串:test.add('!');console.log('test\'scontents:%s',test);运行后,你应该会在控制台看到如下执行结果:结束语希望你看完本教程后能打消顾虑,创建和测试基于C/C++库的自定义Node.js插件被认为是一件小事。您可以使用此技术轻松地将几乎所有C/C++库引入Node.js。如果你愿意,你可以根据实际需要给插件添加更多的功能。std::string提供了大量的方法,我们可以将它们作为练习材料。_____________________________________________________________________________________________有用的链接有兴趣的朋友可以查看以下链接以获得更多与Node.js插件开发、V8和C事件循环库相关的资源和详细信息。?Node.js附加文档?V8文档?libuv(C事件循环库),来自GitHub英语:http://code.tutsplus.com/tutorials/writing-nodejs-addons--cms-21771