libuv is a cross-platform C library for Node.js asynchronous I/O model. It implements Node.js event loop and uses a thread pool to avoid blocking the Node.js event loop with time-consuming I/O operations. The post shares how to use libuv to optimize Dynamsoft barcode addon for Node.js.
Learning Resources
- Node.js C/C++ Addons
- An Introduction to libuv
- Thread pool work scheduling
- .node-gyp\5.5.0\include\node\uv.h
How to Optimize Dynamsoft Barcode Addon for Node.js with Libuv
Prerequisites
- Node.js
- node-gyp:
npm install -g node-gyp
Quickly Build a Node.js C/C++ Barcode Addon
- Create dbr.cc with a function 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)
- Create binding.gyp. Since Dynamsoft Barcode Reader is available for Windows, Linux, and macOS, you can make configuration for all supported platforms:
{ "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" ] }] ] } ] }
- Configure build environment:
node-gyp configure
- Build the project to generate the compiled dbr.node file:
node-gyp build
- Use the addon in 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(); });
Asynchronous Function with Libuv Thread Pool
The above code only implements a synchronous interface. When barcode detection takes too much time, it apparently will block the JavaScript event loop. Therefore, we have to solve this issue with the libuv thread pool.
To send heavy work to the thread pool, we just need to call the API uv_queue_work
(uv_loop_t* loop, uv_work_t* req, uv_work_cb work_cb, uv_after_work_cb after_work_cb).
Create a new function 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); }
Move barcode detection work to a uv_work_cb callback function that executed in a worker thread:
/* * 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; }
Display barcode results in a uv_after_work_cb callback function that executed in the JavaScript thread:
/* * 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; }
Modify dbr.js and run it again:
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(); });
Source Code
https://github.com/yushulx/nodejs-barcode-for-win-linux-mac
The post Making Node.js Async Function with Libuv Thread Pool appeared first on Code Pool.