JavaScript是一种单线程的编程语言。在使用Node.js的时候,如果有耗时的操作,需要放到异步函数中。Node.js的底层使用了libuv,用于实现异步I/O。
学习资源
- Node.js C/C++ Addons
- An Introduction to libuv
- Thread pool work scheduling
- .node-gyp\5.5.0\include\node\uv.h
优化Node.js条形码插件
安装
- Dynamsoft Barcode Reader
- Node.js
- node-gyp:
npm install -g node-gyp
同步接口
先看下同步接口是怎么实现的。
创建dbr.cc,并在里面增加一个函数DecodeFile:
#include <node.h>
#include <node_buffer.h>
#include <string.h>
#include <uv.h>
#include "If_DBR.h"
#include "BarcodeFormat.h"
#include "BarcodeStructs.h"
#include "ErrorCode.h"
using namespace v8;
// Barcode format
const char * GetFormatStr(__int64 format)
{
if (format == CODE_39)
return "CODE_39";
if (format == CODE_128)
return "CODE_128";
if (format == CODE_93)
return "CODE_93";
if (format == CODABAR)
return "CODABAR";
if (format == ITF)
return "ITF";
if (format == UPC_A)
return "UPC_A";
if (format == UPC_E)
return "UPC_E";
if (format == EAN_13)
return "EAN_13";
if (format == EAN_8)
return "EAN_8";
if (format == INDUSTRIAL_25)
return "INDUSTRIAL_25";
if (format == QR_CODE)
return "QR_CODE";
if (format == PDF417)
return "PDF417";
if (format == DATAMATRIX)
return "DATAMATRIX";
return "UNKNOWN";
}
/*
* decodeFile(fileName, barcodeTypes, callback)
*/
void DecodeFile(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// get arguments
String::Utf8Value fileName(args[0]->ToString()); // convert v8 string to char *
char *pFileName = *fileName; // file name
__int64 llFormat = args[1]->IntegerValue(); // barcode types
Local<Function> cb = Local<Function>::Cast(args[2]); // javascript callback function
// initialize Dynamsoft Barcode Reader
int iMaxCount = 0x7FFFFFFF;
ReaderOptions ro = {0};
pBarcodeResultArray pResults = NULL;
ro.llBarcodeFormat = llFormat;
ro.iMaxBarcodesNumPerPage = iMaxCount;
// decode barcode image
int ret = DBR_DecodeFile(pFileName, &ro, &pResults);
if (ret)
printf("Detection error code: %d\n", ret);
int count = pResults->iBarcodeCount;
pBarcodeResult* ppBarcodes = pResults->ppBarcodes;
pBarcodeResult tmp = NULL;
// array for storing barcode results
Local<Array> barcodeResults = Array::New(isolate);
for (int i = 0; i < count; i++)
{
tmp = ppBarcodes[i];
Local<Object> result = Object::New(isolate);
result->Set(String::NewFromUtf8(isolate, "format"), String::NewFromUtf8(isolate, GetFormatStr(tmp->llFormat)));
result->Set(String::NewFromUtf8(isolate, "value"), String::NewFromUtf8(isolate, tmp->pBarcodeData));
barcodeResults->Set(Number::New(isolate, i), result);
}
// release memory of barcode results
DBR_FreeBarcodeResults(&pResults);
// run the callback
const unsigned argc = 1;
Local<Value> argv[argc] = { barcodeResults };
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
}
void Init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "decodeFile", DecodeFile);
}
NODE_MODULE(dbr, Init)
现在可以使用JavaScript接口decodeFile了。创建binding.gyp。针对Windows, Linux和macOS添加不同的头文件和库路径:
{
"targets": [
{
'target_name': "dbr",
'sources': [ "dbr.cc" ],
'conditions': [
['OS=="linux"', {
'defines': [
'LINUX_DBR',
],
'include_dirs': [
"/home/xiao/Dynamsoft/BarcodeReader4.0/Include"
],
'libraries': [
"-lDynamsoftBarcodeReaderx64", "-L/home/xiao/Dynamsoft/BarcodeReader4.0/Redist"
],
'copies': [
{
'destination': 'build/Release/',
'files': [
'/home/xiao/Dynamsoft/BarcodeReader4.0/Redist/libDynamsoftBarcodeReaderx64.so'
]
}]
}],
['OS=="win"', {
'defines': [
'WINDOWS_DBR',
],
'include_dirs': [
"E:\Program Files (x86)\Dynamsoft\Barcode Reader 4.3\Components\C_C++\Include"
],
'libraries': [
"-lE:\Program Files (x86)\Dynamsoft\Barcode Reader 4.3\Components\C_C++\Lib\DBRx64.lib"
],
'copies': [
{
'destination': 'build/Release/',
'files': [
'E:\Program Files (x86)\Dynamsoft\Barcode Reader 4.3\Components\C_C++\Redist\DynamsoftBarcodeReaderx64.dll'
]
}]
}],
['OS=="mac"', {
'defines': [
'MAC_DBR',
],
'include_dirs' : [
"/Applications/Dynamsoft/Barcode\ Reader\ 4.1/Include"
],
'libraries': [
"-lDynamsoftBarcodeReader"
]
}]
]
}
]
}
配置构建环境:
node-gyp configure
编译工程:
node-gyp build
创建dbr.js测试下:
var dbr = require('./build/Release/dbr');
var readline = require('readline');
var fs = require('fs');
var barcodeTypes = 0x3FF | 0x2000000 | 0x8000000 | 0x4000000; // 1D, QRCODE, PDF417, DataMatrix
function decodeFile(fileName) {
dbr.decodeFile(
fileName, barcodeTypes,
function(msg) {
var result = null;
for (index in msg) {
result = msg[index]
console.log("Format: " + result['format']);
console.log("Value : " + result['value']);
console.log("##################");
}
}
);
}
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Please input a barcode image path: ", function(answer) {
decodeFile(answer);
rl.close();
});
异步接口
虽然这样封装接口也是通过回调函数返回的,但是所有的工作都在主线程中,会造成堵塞。解决的方法就是把耗时的工作放到工作线程中。使用接口uv_queue_work(uv_loop_t* loop, uv_work_t* req, uv_work_cb work_cb, uv_after_work_cb after_work_cb)可以把任务放到libuv的线程池中。
新建接口decodeFileAsync:
/*
* decodeFileAsync(fileName, barcodeTypes, callback)
*/
void DecodeFileAsync(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// get arguments
String::Utf8Value fileName(args[0]->ToString()); // file name
char *pFileName = *fileName;
__int64 llFormat = args[1]->IntegerValue(); // barcode types
Local<Function> cb = Local<Function>::Cast(args[2]); // javascript callback function
// initialize BarcodeWorker
BarcodeWorker *worker = new BarcodeWorker;
worker->request.data = worker;
strcpy(worker->filename, pFileName);
worker->callback.Reset(isolate, cb);
worker->llFormat = llFormat;
worker->pResults = NULL;
worker->buffer = NULL;
uv_queue_work(uv_default_loop(), &worker->request, (uv_work_cb)DetectionWorking, (uv_after_work_cb)DetectionDone);
}
void Init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "decodeFile", DecodeFile);
NODE_SET_METHOD(exports, "decodeFileAsync", DecodeFileAsync);
}
把条形码的解码识别工作放到uv_work_cb回调函数中:
/*
* uv_work_cb
*/
static void DetectionWorking(uv_work_t *req)
{
// get the reference to BarcodeWorker
BarcodeWorker *worker = static_cast<BarcodeWorker *>(req->data);
// initialize Dynamsoft Barcode Reader
int iMaxCount = 0x7FFFFFFF;
ReaderOptions ro = {0};
pBarcodeResultArray pResults = NULL;
ro.llBarcodeFormat = worker->llFormat;
ro.iMaxBarcodesNumPerPage = iMaxCount;
// decode barcode image
int ret = 0;
if (worker->buffer)
{
ret = DBR_DecodeStream(worker->buffer, worker->size, &ro, &pResults);
}
else
{
ret = DBR_DecodeFile(worker->filename, &ro, &pResults);
}
if (ret)
printf("Detection error code: %d\n", ret);
// save results to BarcodeWorker
worker->errorCode = ret;
worker->pResults = pResults;
}
uv_after_work_cb回调函数用来在主线程显示结果:
/*
* uv_after_work_cb
*/
static void DetectionDone(uv_work_t *req,int status)
{
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// get the reference to BarcodeWorker
BarcodeWorker *worker = static_cast<BarcodeWorker *>(req->data);
// get barcode results
pBarcodeResultArray pResults = worker->pResults;
int errorCode = worker->errorCode;
int count = pResults->iBarcodeCount;
pBarcodeResult* ppBarcodes = pResults->ppBarcodes;
pBarcodeResult tmp = NULL;
// array for storing barcode results
Local<Array> barcodeResults = Array::New(isolate);
for (int i = 0; i < count; i++)
{
tmp = ppBarcodes[i];
Local<Object> result = Object::New(isolate);
result->Set(String::NewFromUtf8(isolate, "format"), String::NewFromUtf8(isolate, GetFormatStr(tmp->llFormat)));
result->Set(String::NewFromUtf8(isolate, "value"), String::NewFromUtf8(isolate, tmp->pBarcodeData));
barcodeResults->Set(Number::New(isolate, i), result);
}
// release memory of barcode results
DBR_FreeBarcodeResults(&pResults);
// run the callback
const unsigned argc = 1;
Local<Value> argv[argc] = {barcodeResults};
Local<Function> cb = Local<Function>::New(isolate, worker->callback);
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
// release memory of BarcodeWorker
delete worker;
}
修改dbr.js调用异步接口:
var dbr = require('./build/Release/dbr');
var readline = require('readline');
var fs = require('fs');
var barcodeTypes = 0x3FF | 0x2000000 | 0x8000000 | 0x4000000; // 1D, QRCODE, PDF417, DataMatrix
function decodeFileAsync(fileName) {
dbr.decodeFileAsync(
fileName, barcodeTypes,
function(msg) {
var result = null;
for (index in msg) {
result = msg[index]
console.log("Format: " + result['format']);
console.log("Value : " + result['value']);
console.log("##################");
}
}
);
}
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Please input a barcode image path: ", function(answer) {
decodeFileAsync(answer);
rl.close();
});