Nodejs Native AddOn的编写

本文地址 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, 贴图如下代码

#include <node.h>
#include <v8.h>
using namespace v8;


void init(Handle<Object> exports) {
 
}
NODE_MODULE(mq, init)

这是一个“空”的addon,啥事都没干。 NODE_MODULE宏的第一个参数是该模块的名称;第二个参数是初始化函数init,此函数在addon加载后调用。

然后在adapter.cc同目录中新建文件building.gyp,它的内容是JSON格式

{
  "targets": [
    {
      "target_name": "mq",
      "sources": [ "adapter.cc" ]
    }
  ]
}
需要注意的是, target_name必须和NODE_MODULE的第一个参数相同。

然后就可以在此目录下,使用gyp编译了

node-gyp configure
node-gyp rebuild

编译的结果在build/release目录下,文件的扩展名是 *.node, 而文件名就是之前指定的模块名。

将此*.node文件拷贝到nodejs工程中的node_modules文件夹下,就可以进行加载了。

var MQ = require('mq.node');
console.log(MQ);


Addon中注册方法供NodeJS调用

在Addon中被NodeJS调用的函数原型必须是 Handle<Value> method(const Arguments& args), 在模块初始化的时候注册此方法。如:

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)->GetFunction());
}
在nodejs中即可调用此方法
var MQ = require('mq.node');
MQ.XXXXXX( 2, false, 'Text');

从javascript这样的弱类型语言向C强类型语言传递参数,在输入时需要做好类型检查与类型转换。


Addon中回调NodeJS方法

首先在NodeJS中将需要被回调的函数地址通过参数传入。

MQ.setLogCallback(function (level, message) {
	console.log('[' + level + '] : ' + message)
});

在Addon中,将传递进来的回调函数进行保存

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());
}

在Addon中,当需要回调此函数的时候,直接调用即可。如

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);
}


多线程环境下回调 

NodeJS中的V8引擎是以单线程执行的,回调JS方法也必须在V8的主线程中进行,否则会发生未知的后果甚至crash掉整个进程。NodeJS底层的libuv提供了相应的通知机制来实现主线程中的调用。

首先需要定义 uv_async_t 变量

static uv_async_t s_async = {0};
在主线程中初始化此变量,并注册在主线程中此通知触发时回调的方法。此步骤可以在init中执行。

uv_async_init( uv_default_loop(), &s_async, onCallback);

而onCallback方法则在主线程中,通知发生后执行
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);
	}
}

在任何线程中,都可以通过 uv_async_send来触发此回调的执行。

uv_async_send(&s_async);

当不再需要回调的时候,可以调用 uv_close来取消注册此回调方法。

uv_close( &s_async, NULL);

这里特别需要注意的是uv_async_send触发回调的次数并不是一一对应的。它只能保证最少一次的触发。可能会出现这样一种情况,连续调用了3次uv_async_send方法,但回调只被触发了一次(调用第1、2、3次的时候,NodeJS的主循环可能忙于其它处理而直到检测到此通知时,3次调用都已经发生了,而此时只会进行一次回调)。针对这种情况,应该设计相应的队列结构来传递数据到回调中依次处理。

 

异步调用

NodeJS的主线程只负责event loop和V8的执行,如果addon中某个导出方法在调用时会发生阻塞,会严重地影响到NodeJS的整体性能。因此,libuv设计了异步调用的方式--将阻塞类操作放入其它线程中处理,在处理完成后回调。

例如,JS调用如下导出方法

AddOn.lookupIpCountry( ip, function(countryCode){
	// get the country code
	// ...
});

在AddOn中,定义一个结构体在异步调用中传递数据。

struct LookupIpCountryBaton {
	uv_work_t work;
	Persistent<Function> callback;
	char ip[IP_LEN];
	char country_code[COUNTRY_CODE_LEN];
};

导出方法首先保存回调函数,并验证和解析传入参数

// 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();
}


这里最关键的是 uv_queue_work, 它将请求压入队列交由其它线程执行,同时指定在线程中执行的函数( lookupIpCountryAsyc),亦指定了调用结束后完成的函数( lookupIpCountryCompleted)


lookupIpCountryAsyc函数中,进行阻塞调用。这里要注意,此函数不是在主线程中运行,所以不能访问或者调用任何V8有关的函数或数据。

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);
}

当此函数执行完后, lookupIpCountryCompleted函数会在主线程中被执行,完成回调和清理工作。

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;
}


本文地址 http://blog.csdn.net/wangjia184/article/details/18940165




使用Node.js的Addon可以方便地加载和调用DLL(动态链接库)。 首先,我们需要创建一个C++的Addon模块。在该模块中,我们可以使用`LoadLibrary`函数加载DLL,并使用`GetProcAddress`函数获取DLL中定义的函数指针。 下面是一个示例代码: ```cpp #include <node.h> // 声明DLL函数指针类型 typedef int (*MY_FUNCTION)(int); // 在Addon模块中导出的函数 void CallDLLFunction(const v8::FunctionCallbackInfo<v8::Value>& args) { // 加载DLL HMODULE hModule = LoadLibrary("path/to/dll"); if (hModule) { // 获取DLL中的函数指针 MY_FUNCTION myFunction = (MY_FUNCTION)GetProcAddress(hModule, "FunctionName"); if (myFunction) { // 调用DLL函数 int result = myFunction(10); // 返回结果 args.GetReturnValue().Set(result); } else { // 获取函数指针失败 args.GetReturnValue().Set(-1); } // 卸载DLL FreeLibrary(hModule); } else { // 加载DLL失败 args.GetReturnValue().Set(-1); } } // 注册Addon模块 void Initialize(v8::Local<v8::Object> exports) { NODE_SET_METHOD(exports, "callDLLFunction", CallDLLFunction); } NODE_MODULE(addon, Initialize) ``` 在Node.js中,我们可以使用Addon模块中导出的`callDLLFunction`函数来调用DLL。 ```javascript const addon = require('./addon'); // 调用DLL函数 const result = addon.callDLLFunction(); console.log(result); ``` 需要确保将DLL所在的路径替换为`LoadLibrary`函数中的实际路径,并将`GetProcAddress`函数中的`FunctionName`替换为DLL中实际的函数名。 通过这种方式,我们可以利用Node.js的Addon功能方便地加载和调用DLL,从而扩展Node.js的功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值