本文地址 http://blog.csdn.net/wangjia184/article/details/18940165
如果要在nodejs中調用動態鏈接庫中的導出方法,或者從動態鏈接庫中回調nodejs中的某個方法,可以採用 node-ffi(https://github.com/rbranson/node-ffi )。不過我試了很久都沒有成功,貌似ffi對於回調的支持有問題,無法正確區分 _stdcall 與 _cdecl。而另一種實現方式就非常簡單直接了,通過編寫nodejs addon的方式直接實現。
nodejs中的addon使用C編寫,其編譯鏈接的工具鏈不是常見的makefile autoconf之類,而是從Chromium移植來的node-gyp。所以,如果直接將複雜的C/C++代碼在addon中實現,容易產生編譯或者鏈接衝突。比較簡單的方式是,addon只作爲adapter使用,在addon中通過dlopen/LoadLibrary去操縱動態鏈接庫或者回調js.
http://nodejs.org/api/addons.html 有實現Addon的基本講解。
AddOn的基本結構
首先新建adapter.cc, 貼圖如下代碼
<span style="font-size:18px;">#include <node.h>
#include <v8.h>
using namespace v8;
void init(Handle<Object> exports) {
}
NODE_MODULE(mq, init)</span>
這是一個“空”的addon,啥事都沒幹。NODE_MODULE宏的第一個參數是該模塊的名稱;第二個參數是初始化函數init,此函數在addon加載後調用。
然後在adapter.cc同目錄中新建文件building.gyp,它的內容是JSON格式
<span style="font-size:18px;">{
"targets": [
{
"target_name": "mq",
"sources": [ "adapter.cc" ]
}
]
}</span>
需要注意的是,target_name必須和NODE_MODULE的第一個參數相同。
然後就可以在此目錄下,使用gyp編譯了
<span style="font-size:18px;">node-gyp configure
node-gyp rebuild</span>
編譯的結果在build/release目錄下,文件的擴展名是 *.node, 而文件名就是之前指定的模塊名。
將此*.node文件拷貝到nodejs工程中的node_modules文件夾下,就可以進行加載了。
<span style="font-size:18px;">var MQ = require('mq.node');
console.log(MQ);</span>
Addon中註冊方法供NodeJS調用
在Addon中被NodeJS調用的函數原型必須是 Handle<Value> method(const Arguments& args), 在模塊初始化的時候註冊此方法。如:
<span style="font-size:18px;">Handle<Value> XXXXXX(const Arguments& args) {
HandleScope scope;
return scope.Close(Undefined());
}
void init(Handle<Object> exports) {
exports->Set(String::NewSymbol("XXXXXX"),
FunctionTemplate::New(XXXXXX<span style="font-family:Arial, Helvetica, sans-serif;">)->GetFunction());</span>
}</span>
在nodejs中即可調用此方法<span style="font-size:18px;">var MQ = require('mq.node');
MQ.XXXXXX( 2, false, 'Text');</span>
從javascript這樣的弱類型語言向C強類型語言傳遞參數,在輸入時需要做好類型檢查與類型轉換。
Addon中回調NodeJS方法
首先在NodeJS中將需要被回調的函數地址通過參數傳入。
<span style="font-size:18px;">MQ.setLogCallback(function (level, message) {
console.log('[' + level + '] : ' + message)
});</span>
在Addon中,將傳遞進來的回調函數進行保存
<span style="font-size:18px;">static Persistent<Function> s_logCallback;
Handle<Value> setLogCallback(const Arguments& args) {
HandleScope scope;
if (args.Length() < 1 || !args[0]->IsFunction() ) {
return ThrowException(Exception::TypeError(String::New("Invalid parameter.")));
}
s_logCallback.Dispose();
s_logCallback = Persistent<Function>::New(Local<Function>::Cast(args[0]));
return scope.Close(Undefined());
}
</span>
在Addon中,當需要回調此函數的時候,直接調用即可。如
<span style="font-size:18px;">if( !s_logCallback.IsEmpty() ){
const unsigned argc = 2;
Local<Value> argv[argc] = {
Local<Value>::New(Number::New(1)) ,
Local<Value>::New(String::New("Test Message"))
};
s_logCallback->Call(Context::GetCurrent()->Global(), argc, argv);
}</span>
多線程環境下回調
NodeJS中的V8引擎是以單線程執行的,回調JS方法也必須在V8的主線程中進行,否則會發生未知的後果甚至crash掉整個進程。NodeJS底層的libuv提供了相應的通知機制來實現主線程中的調用。
首先需要定義 uv_async_t 變量
<span style="font-size:18px;">static uv_async_t s_async = {0};</span>
在主線程中初始化此變量,並註冊在主線程中此通知觸發時回調的方法。此步驟可以在init中執行。<span style="font-size:18px;"><a target=_blank name="baidusnap1"></a><strong style="color:black;background-color:#A0FFFF">uv_async_init</strong>( uv_default_loop(), &s_async, onCallback);</span>
而onCallback方法則在主線程中,通知發生後執行
<span style="font-size:18px;">void onCallback(uv_async_t* handle, int status){
if( !s_logCallback.IsEmpty() ){
const unsigned argc = 2;
Local<Value> argv[argc] = {
Local<Value>::New(Number::New(1)) ,
Local<Value>::New(String::New("Callback is happening"))
};
s_logCallback->Call(Context::GetCurrent()->Global(), argc, argv);
}
}</span>
在任何線程中,都可以通過uv_async_send來觸發此回調的執行。
<span style="font-size:18px;">uv_async_send(&s_async);</span>
當不再需要回調的時候,可以調用uv_close來取消註冊此回調方法。
<span style="font-size:18px;">uv_close( &s_async, NULL);</span>
這裏特別需要注意的是,uv_async_send觸發回調的次數並不是一一對應的。它只能保證最少一次的觸發。可能會出現這樣一種情況,連續調用了3次uv_async_send方法,但回調只被觸發了一次(調用第1、2、3次的時候,NodeJS的主循環可能忙於其它處理而直到檢測到此通知時,3次調用都已經發生了,而此時只會進行一次回調)。針對這種情況,應該設計相應的隊列結構來傳遞數據到回調中依次處理。
異步調用
NodeJS的主線程只負責event loop和V8的執行,如果addon中某個導出方法在調用時會發生阻塞,會嚴重地影響到NodeJS的整體性能。因此,libuv設計了異步調用的方式--將阻塞類操作放入其它線程中處理,在處理完成後回調。
例如,JS調用如下導出方法
<span style="font-size:18px;">AddOn.lookupIpCountry( ip, function(countryCode){
// get the country code
// ...
});</span>
在AddOn中,定義一個結構體在異步調用中傳遞數據。
<span style="font-size:18px;">struct LookupIpCountryBaton {
uv_work_t work;
Persistent<Function> callback;
char ip[IP_LEN];
char country_code[COUNTRY_CODE_LEN];
};</span>
導出方法首先保存回調函數,並驗證和解析傳入參數
<span style="font-size:18px;">// lookup country by ip
// 1st argument is ip address
// 2nd argument is the callback function
Handle<Value> lookupIpCountry(const Arguments& args) {
HandleScope scope;
if (args.Length() < 2 ||
!args[1]->IsFunction() ||
(!args[0]->IsStringObject() && !args[0]->IsString()) ) {
return ThrowException(Exception::TypeError(String::New("Invalid parameter.")));
}
String::Utf8Value param1(args[0]->ToString());
char * ip = *param1;
LookupIpCountryBaton * baton = new LookupIpCountryBaton();
baton->work.data = baton;
memset( baton->country_code, 0, COUNTRY_CODE_LEN);
memset( baton->ip, 0, IP_LEN);
strncpy( baton->ip, ip, IP_LEN);
baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
uv_queue_work( uv_default_loop(), &baton->work, lookupIpCountryAsync, lookupIpCountryCompleted);
return Undefined();
}
</span>
這裏最關鍵的是uv_queue_work, 它將請求壓入隊列交由其它線程執行,同時指定在線程中執行的函數(lookupIpCountryAsyc),亦指定了調用結束後完成的函數(lookupIpCountryCompleted)
在lookupIpCountryAsyc函數中,進行阻塞調用。這裏要注意,此函數不是在主線程中運行,所以不能訪問或者調用任何V8有關的函數或數據。
<span style="font-size:18px;">void lookupIpCountryAsync(uv_work_t * work){
LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data;
// block thread for 3 seconds
sleep(3);
// save the result
strncpy( baton->country_code, "CN", COUNTRY_CODE_LEN - 1);
}</span>
當此函數執行完後,lookupIpCountryCompleted函數會在主線程中被執行,完成回調和清理工作。
<span style="font-size:18px;">void lookupIpCountryCompleted(uv_work_t * work, int){
LookupIpCountryBaton * baton = (LookupIpCountryBaton*)work->data;
const unsigned argc = 1;
Local<Value> argv[argc] = {
Local<Value>::New(String::New(baton->country_code)) ,
};
baton->callback->Call(Context::GetCurrent()->Global(), argc, argv);
baton->callback.Dispose();
delete baton;
}</span>
本文地址 http://blog.csdn.net/wangjia184/article/details/18940165