第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號(hào)安全,請(qǐng)及時(shí)綁定郵箱和手機(jī)立即綁定
已解決430363個(gè)問(wèn)題,去搜搜看,總會(huì)有你想問(wèn)的

如何編寫(xiě) Node.js 擴(kuò)展

如何編寫(xiě) Node.js 擴(kuò)展

www說(shuō) 2019-03-14 10:10:59
如何編寫(xiě) Node.js 擴(kuò)展
查看完整描述

3 回答

?
ibeautiful

TA貢獻(xiàn)1993條經(jīng)驗(yàn) 獲得超6個(gè)贊

一、編寫(xiě)Node.js原生擴(kuò)展

Node.js是一個(gè)強(qiáng)大的平臺(tái),理想狀態(tài)下一切都都可以用javascript寫(xiě)成。然而,你可能還會(huì)用到許多遺留的庫(kù)和系統(tǒng),這樣的話使用c++編寫(xiě)Node.JS擴(kuò)展會(huì)是一個(gè)不錯(cuò)的注意。

以下所有例子的源代碼可在node擴(kuò)展示例中找到 。

編寫(xiě)Node.js C + +擴(kuò)展很大程度上就像是寫(xiě)V8的擴(kuò)展; Node.js增加了一些接口,但大部分時(shí)間你都是在使原始的V8數(shù)據(jù)類(lèi)型和方法,為了理解以下的代碼,你必須首先閱讀V8引擎嵌入指南。

Javascript版本的Hello World

在講解C++版本的例子之前,先讓我們來(lái)看看在Node.js中用Javascript編寫(xiě)的等價(jià)模塊是什么樣子。這是一個(gè)最簡(jiǎn)單的Hello World,也不是通過(guò)HTTP,但它展示了node模塊的結(jié)構(gòu),而其接口也和大多數(shù)C++擴(kuò)展要提供的接口差不多:

HelloWorldJs = function() {

this.m_count = 0;

};

HelloWorldJs.prototype.hello = function()

{

this.m_count++;

return “Hello World”;

};

exports.HelloWorldJs = HelloWorldJs;

正如你所看到的,它使用prototype為HelloWorldJs類(lèi)創(chuàng)建了一個(gè)新的方法。請(qǐng)注意,上述代碼通過(guò)將HelloWorldJS添加到exports變量來(lái)暴露構(gòu)造函數(shù)。

要在其他地方使用該模塊,請(qǐng)使用如下代碼:

var helloworld = require(‘helloworld_js’);

var hi = new helloworld.HelloWorldJs();

console.log(hi.hello()); // prints “Hello World” to stdout

C++版本的Hello World

要開(kāi)始編寫(xiě)C++擴(kuò)展,首先要能夠編譯Node.js(請(qǐng)注意,我們使用的是Node.js 2.0版本)。本文所講內(nèi)容應(yīng)該兼容所有未來(lái)的0.2.x版本。一旦編譯安裝完node,編譯模塊就不在需要額外的東西了。

完整的源代碼可以在這里找到 。在使用Node.js或V8之前,我們需要包括相關(guān)的頭文件:

#include <v8.h>

#include <node.h>

using namespace node;

using namespace v8;

在本例子中我直接使用了V8和node的命名空間,使代碼更易于閱讀。雖然這種用法和谷歌的自己的C++編程風(fēng)格指南相悖,但由于你需要不停的使用V8定義的類(lèi)型,所以目前為止的大多數(shù)node的擴(kuò)展仍然使用了V8的命名空間。

接下來(lái),聲明HelloWorld類(lèi)。它繼承自node::ObjectWrap類(lèi) ,這個(gè)類(lèi)提供了幾個(gè)如引用計(jì)數(shù)、在V8內(nèi)部傳遞contex等的實(shí)用功能。一般來(lái)說(shuō),所有對(duì)象應(yīng)該繼承ObjectWrap:

class HelloWorld: ObjectWrap

{

private:

int m_count;

public:

聲明類(lèi)之后,我們定義了一個(gè)靜態(tài)成員函數(shù),用來(lái)初始化對(duì)象并將其導(dǎo)入Node.js提供的target對(duì)象中。設(shè)個(gè)函數(shù)基本上是告訴Node.js和V8你的類(lèi)是如何創(chuàng)建的,和它將包含什么方法:

static Persistent<FunctionTemplate> s_ct;

static void Init(Handle<Object> target)

{

HandleScope scope;

Local<FunctionTemplate> t = FunctionTemplate::New(New);

s_ct = Persistent<FunctionTemplate>::New(t);

s_ct->InstanceTemplate()->SetInternalFieldCount(1);

s_ct->SetClassName(String::NewSymbol(“HelloWorld”));

NODE_SET_PROTOTYPE_METHOD(s_ct, “hello”, Hello);

target->Set(String::NewSymbol(“HelloWorld”),

s_ct->GetFunction());

}

在上面這個(gè)函數(shù)中target參數(shù)將是模塊對(duì)象,即你的擴(kuò)展將要載入的地方。(譯著:這個(gè)函數(shù)將你的對(duì)象及其方法連接到
這個(gè)模塊對(duì)象,以便外界可以訪問(wèn))首先我們?yōu)镹ew方法創(chuàng)建一個(gè)FunctionTemplate,將于稍后解釋。我們還為該對(duì)象添加一個(gè)內(nèi)部字段,并命
名為HelloWorld。然后使用NODE_SET_PROTOTYPE_METHOD宏將hello方法綁定到該對(duì)象。最后,一旦我們建立好這個(gè)函數(shù)模板后,將他分配給target對(duì)象的HelloWorld屬性,將類(lèi)暴露給用戶。

接下來(lái)的部分是一個(gè)標(biāo)準(zhǔn)的C++構(gòu)造函數(shù):

HelloWorld() :

m_count(0)

{

}

~HelloWorld()

{

}

接下來(lái),在::New 方法中V8引擎將調(diào)用這個(gè)簡(jiǎn)單的C++構(gòu)造函數(shù):

static Handle<Value> New(const Arguments& args)

{

HandleScope scope;

HelloWorld* hw = new HelloWorld();

hw->Wrap(args.This());

return args.This();

}

此段代碼相當(dāng)于上面Javascript代碼中使用的構(gòu)造函數(shù)。它調(diào)用new HelloWorld
創(chuàng)造了一個(gè)普通的C++對(duì)象,然后調(diào)用從ObjectWrap繼承的Wrap方法,
它將一個(gè)C++HelloWorld類(lèi)的引用保存到args.This()的值中。在包裝完成后返回args.This(),整個(gè)函數(shù)的行為和
javascript中的new運(yùn)算符類(lèi)似,返回this指向的對(duì)象。

現(xiàn)在我們已經(jīng)建立了對(duì)象,下面介紹在Init函數(shù)中被綁定到hello的函數(shù):

static Handle<Value> Hello(const Arguments& args)

{

HandleScope scope;

HelloWorld* hw = ObjectWrap::Unwrap<HelloWorld>(args.This());

hw->m_count++;

Local<String> result = String::New(“Hello World”);

return scope.Close(result);

}

函數(shù)中首先使用ObjectWrap模板的方法提取出指向HelloWorld類(lèi)的指針,然后和javascript版本的HelloWorld一樣遞增計(jì)數(shù)器。我們新建一個(gè)內(nèi)容為“HelloWorld”的v8字符串對(duì)象,然后在關(guān)閉本地作用域的時(shí)候返回這個(gè)字符串。

上面的代碼實(shí)際上只是針對(duì)v8的接口,最終我們還需要讓Node.js知道如何動(dòng)態(tài)加載我們的代碼。為了使Node.js的擴(kuò)展可以在執(zhí)行時(shí)從動(dòng)態(tài)鏈接庫(kù)加載,需要有一個(gè)dlsym函數(shù)可以識(shí)別的符號(hào),所以執(zhí)行編寫(xiě)如下代碼:

extern “C” {

static void init (Handle<Object> target)

{

HelloWorld::Init(target);

}

NODE_MODULE(helloworld, init);

}

由于c++的符號(hào)命名規(guī)則,我們使用extern
C,以便該符號(hào)可以被dysym識(shí)別。init方法是Node.js加載模塊后第一個(gè)調(diào)用的函數(shù),如果你有多個(gè)類(lèi)型,請(qǐng)全部在這里初始化。
NODE_MODULE宏用來(lái)填充一個(gè)用于存儲(chǔ)模塊信息的結(jié)構(gòu)體,存儲(chǔ)的信息如模塊使用的API版本。這些信息可以用來(lái)防止未來(lái)因API不兼容導(dǎo)致的崩
潰。

到此,我們已經(jīng)完成了一個(gè)可用的C++ NodeJS擴(kuò)展。

Node.js也提供了一個(gè)用于構(gòu)建模塊的簡(jiǎn)單工具:
node-waf首先編寫(xiě)一個(gè)包含擴(kuò)展編譯方法的wscript文件,然后執(zhí)行node-waf configure &&
node-waf build完成模塊的編譯和鏈接工作。對(duì)于這個(gè)helloworld的例子來(lái)說(shuō),wscript內(nèi)容如下:

def set_options(opt):

opt.tool_options(“compiler_cxx”)

def configure(conf):

conf.check_tool(“compiler_cxx”)

conf.check_tool(“node_addon”)

def build(bld):

obj = bld.new_task_gen(“cxx”, “shlib”, “node_addon”)

obj.cxxflags = [“-g”, “-D_FILE_OFFSET_BITS=64”, “-D_LARGEFILE_SOURCE”, “-Wall”]

obj.target = “helloworld”

obj.source = “helloworld.cc”

異步IO的HelloWorld

對(duì)于實(shí)際的應(yīng)用來(lái)說(shuō),HelloWorld的示例太過(guò)簡(jiǎn)單了一些,Node.js主要的優(yōu)勢(shì)是提供異步IO。
Node.js內(nèi)部通過(guò)libeio將會(huì)產(chǎn)生阻塞的操作全都放入線程池中執(zhí)行。如果需要和遺留的c庫(kù)交互,通常需要使用異步IO來(lái)為javascript
代碼提供回調(diào)接口。

通常的模式是提供一個(gè)回調(diào),在異步操作完成時(shí)被調(diào)用——你可以在整個(gè)Node.js的API中看到這種模式。
Node.js的filesystem模塊提供了一個(gè)很好的例子,其中大多數(shù)的函數(shù)都在操作完成后通過(guò)調(diào)用回調(diào)函數(shù)來(lái)傳遞數(shù)據(jù)。和許多傳統(tǒng)的GUI框架一
樣,Node.js只在主線程中執(zhí)行JavaScript,因此主線程以外的任何操作都不應(yīng)該直接和V8或Javascript交互。

同樣helloworld_eio.cc源代碼在GitHub上。我只強(qiáng)調(diào)和原來(lái)HelloWorld之間的差異,其中大部分代碼保持不變,變化集中在Hello方法中:

static Handle<Value> Hello(const Arguments& args)

{

HandleScope scope;

REQ_FUN_ARG(0, cb);

HelloWorldEio* hw = ObjectWrap::Unwrap<HelloWorldEio>(args.This());

在Hello函數(shù)的入口處 ,我們使用宏從參數(shù)列表的第一個(gè)位置獲取回調(diào)函數(shù),在下一節(jié)中將詳細(xì)介紹。然后,我們使用相同的Unwarp方法提取指向類(lèi)對(duì)象的指針。

hello_baton_t *baton = new hello_baton_t();

baton->hw = hw;

baton->increment_by = 2;

baton->sleep_for = 1;

baton->cb = Persistent<Function>::New(cb);

這里我們創(chuàng)建一個(gè)baton結(jié)構(gòu),并將各種參數(shù)保存在里面。請(qǐng)注意,我們?yōu)榛卣{(diào)函數(shù)創(chuàng)建了一個(gè)永久引用,因?yàn)槲覀兿胍诔霎?dāng)前函數(shù)作用域的地方使用它。如果不這么做,在本函數(shù)結(jié)束后將無(wú)法再調(diào)用回調(diào)函數(shù)。

hw->Ref();

eio_custom(EIO_Hello, EIO_PRI_DEFAULT, EIO_AfterHello, baton);

ev_ref(EV_DEFAULT_UC);

return Undefined();

}

如下代碼是真正的重點(diǎn)。首先,我們?cè)黾親elloWorld對(duì)象的引用計(jì)數(shù),這樣在其他線程執(zhí)行的時(shí)候他就不會(huì)被回收。
函數(shù)eio_custom接受兩個(gè)函數(shù)指針作為參數(shù)。EIO_Hello函數(shù)將在線程池中執(zhí)行,然后EIO_AfterHello函數(shù)將回到在“主線程”
中執(zhí)行。我們的baton結(jié)構(gòu)也被傳遞進(jìn)各函數(shù),這些函數(shù)可以使用baton結(jié)構(gòu)中的數(shù)據(jù)完成相關(guān)的操作。同時(shí),我們也增加event
loop的引用。這很重要,因?yàn)槿绻鹐vent
loop無(wú)事可做,Node.js就會(huì)退出。最終,函數(shù)返回Undefined,因?yàn)檎嬲墓ぷ鲗⒃谄渌€程中完成。

static int EIO_Hello(eio_req *req)

{

hello_baton_t *baton = static_cast<hello_baton_t *>(req->data);

sleep(baton->sleep_for);

baton->hw->m_count += baton->increment_by;

return 0;

}

這個(gè)回調(diào)函數(shù)將在libeio管理的線程中執(zhí)行。首先,解析出baton結(jié)構(gòu),這樣可以訪問(wèn)之前設(shè)置的各種參數(shù)。然后
sheep
baton->sleep_for秒,這么做是安全的,因?yàn)檫@個(gè)函數(shù)運(yùn)行在獨(dú)立的線程中并不會(huì)阻塞主線程中javascript的執(zhí)行。然后我們的
增計(jì)數(shù)器,在實(shí)際的系統(tǒng)中,這些操作通常需要使用Lock/Mutex進(jìn)行同步。

當(dāng)上述方法返回后,libeio將會(huì)通知主線程它需要在主線成上執(zhí)行代碼,此時(shí)EIO_AfterHello將會(huì)被調(diào)用。

static int EIO_AfterHello(eio_req *req)

{

HandleScope scope;

hello_baton_t *baton = static_cast<hello_baton_t *>(req->data);

ev_unref(EV_DEFAULT_UC);

baton->hw->Unref();

進(jìn)度此函數(shù)時(shí),我們提取出baton結(jié)構(gòu),刪除事件循環(huán)的引用,并減少HelloWorld對(duì)象的引用。

Local<Value> argv[1];

argv[0] = String::New(“Hello World”);

TryCatch try_catch;

baton->cb->Call(Context::GetCurrent()->Global(), 1, argv);

if (try_catch.HasCaught()) {

FatalException(try_catch);

}

新建要傳遞給回調(diào)函數(shù)的字符串參數(shù),并放入字符串?dāng)?shù)組中。然后我們調(diào)用回調(diào)傳遞一個(gè)參數(shù),并檢測(cè)可能拋出的異常。

baton->cb.Dispose();

delete baton;

return 0;

}

在執(zhí)行過(guò)回調(diào)之后,應(yīng)該銷(xiāo)毀持久引用,然后刪除之前創(chuàng)建的baton結(jié)構(gòu)。

最后,你可以使用如下形式在Javascript中使用該模塊:

var helloeio = require(‘./helloworld_eio’);

hi = new helloeio.HelloWorldEio();

hi.hello(function(data){

console.log(data);

});

參數(shù)傳遞與解析

除了HelloWorld之外,你還需要理解最后一個(gè)問(wèn)題:參數(shù)的處理。在helloWorld EIO例子中,我們使用一個(gè)REQ_FUN_ARG宏,然我們看看這個(gè)宏到底都做些什么。

#define REQ_FUN_ARG(I, VAR) \

if (args.Length() <= (I) || !args[I]->IsFunction()) \

return ThrowException(Exception::TypeError( \

String::New(“Argument ” #I ” must be a function”))); \

Local<Function> VAR = Local<Function>::Cast(args[I]);

就像Javascript中的argument變量,v8使用數(shù)組傳遞所有的參數(shù)。由于沒(méi)有嚴(yán)格的類(lèi)型限制,所以傳遞給函數(shù)的參數(shù)數(shù)目可能和期待的不同。為了對(duì)用戶友好,使用如下的宏檢測(cè)一下參數(shù)數(shù)組的長(zhǎng)度并判斷參數(shù)是否是正確的類(lèi)型。如果傳遞了錯(cuò)誤的參數(shù)類(lèi)型,該宏將會(huì)拋出TypeError異常。為簡(jiǎn)化參數(shù)的解析,目前為止大多數(shù)的Node.js擴(kuò)展都有一些本地作用域內(nèi)的宏,用于特定類(lèi)型參數(shù)的檢測(cè)。

二、揭秘node.js事件

要使用NodeJS,你需要知道一個(gè)重要的東西:事件(events)。Node中有很多對(duì)象都可以觸發(fā)事件,Node
的文檔中有很多示例。但文檔也許并不能清晰的講解如何編寫(xiě)自定義事件以及監(jiān)聽(tīng)函數(shù)。對(duì)于一些簡(jiǎn)單的程序你可以不使用自定義事件,但這樣很難應(yīng)對(duì)復(fù)雜的應(yīng)
用。那么如何編寫(xiě)自定義事件?首先需要了解的是在node.js中的’events’模塊。

快速概覽

要訪問(wèn)此模塊,只需使用如下語(yǔ)句:

require(‘events’)
requires(‘events’).EventEmitter

特別說(shuō)明,node中所有能觸發(fā)事件的對(duì)象基本上都是后者的實(shí)例。讓我們創(chuàng)建一個(gè)簡(jiǎn)單的演示程序Dummy:

dummy.js

view plaincopy to clipboardprint?

// basic imports
var events = require(‘events’);

// for us to do a require later
module.exports = Dummy;

function Dummy() {
events.EventEmitter.call(this);
}
10.

11. // inherit events.EventEmitter

12. Dummy.super_ = events.EventEmitter;

13. Dummy.prototype = Object.create(events.EventEmitter.prototype, {

14. constructor: {
15. value: Dummy,
16. enumerable: false
17. }

18. });

// basic imports

var events = require(‘events’);

// for us to do a require later

module.exports = Dummy;

function Dummy() {

events.EventEmitter.call(this);

}

// inherit events.EventEmitter

Dummy.super_ = events.EventEmitter;

Dummy.prototype = Object.create(events.EventEmitter.prototype, {

constructor: {

value: Dummy,

enumerable: false

}

});

上述代碼中重點(diǎn)展示如何使用EventEmitter擴(kuò)充對(duì)象,并從中繼承所有的原型對(duì)象,方法…等等。

現(xiàn)在,我們假設(shè)Dummy有一個(gè)cooking()的方法,一旦把食物做熟之后它會(huì)觸發(fā)’cooked’事件,并調(diào)用一個(gè)名為’eat’的回調(diào)函數(shù)。

dummy-cooking.js

view plaincopy to clipboardprint?

Dummy.prototype.cooking = function(chicken) {
var self = this;
self.chicken = chicken;
self.cook = cook(); // assume dummy function that’ll do the cooking
self.cook(chicken, function(cooked_chicken) {
self.chicken = cooked_chicken;
self.emit(‘cooked’, self.chicken);
});

10. return self;

11. }

Dummy.prototype.cooking = function(chicken) {

var self = this;

self.chicken = chicken;

self.cook = cook(); // assume dummy function that’ll do the cooking

self.cook(chicken, function(cooked_chicken) {

self.chicken = cooked_chicken;

self.emit(‘cooked’, self.chicken);

});

return self;

}



查看完整回答
反對(duì) 回復(fù) 2019-03-21
  • 3 回答
  • 0 關(guān)注
  • 631 瀏覽

添加回答

舉報(bào)

0/150
提交
取消
微信客服

購(gòu)課補(bǔ)貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動(dòng)學(xué)習(xí)伙伴

公眾號(hào)

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號(hào)