Building Dart Native Extension with Dynamsoft Barcode SDK on Windows
I just finished reading the article — Native Extensions for the Standalone Dart VM. In this post, I will share my experience about how to build a Dart native extension from scratch with Dynamsoft C/C++ barcode SDK on Windows.
Why Asynchronous Extension
Dart is a single-threaded programming language, similar to JavaScript. Any code with time-consuming operations will block the main Dart thread, freezing the program. The asynchronous extension runs a native function on a separate thread, scheduled by the Dart VM. Invoking third-party barcode APIs in asynchronous extension is recommended.
Building Dart Native Extension using Visual Studio 2015
Install Dart SDK and Dynamsoft Barcode Reader. Learn the sample code of native extension that included in Dart SDK repository.
A Dart native extension contains a *.dart file and a *.dll file on Windows. The *.dart file defines a class that provides functions to forward arguments to the native port. A reply port is used to receive the message back from C function. Create sample_asynchronous_extension.dart:
library sample_asynchronous_extension;
import 'dart:async';
import 'dart:isolate';
import 'dart-ext:sample_extension';
class BarcodeReader {
static SendPort _port;
Future<List> readBarcode(String filename) {
var
completer = new
Completer();
var
replyPort = new
RawReceivePort();
var
args = new
List(2);
args[0] = filename;
args[1] = replyPort.sendPort;
_servicePort.send(args);
replyPort.handler = (result) {
replyPort.close();
if
(result != null) {
completer.complete(result);
} else
{
completer.completeError(new
Exception("Reading barcode failed"));
}
};
return
completer.future;
}
SendPort get _servicePort {
if
(_port == null) {
_port = _newServicePort();
}
return
_port;
}
SendPort _newServicePort() native "BarcodeReader_ServicePort";
}
Launch Visual Studio 2015 to create an empty Win32 project. Set DLL as the Application type and check the option Empty project. Add the paths of include and lib files:
Add dependencies dart.lib and DBRx64.lib:
Add preprocessor DART_SHARED_LIB:
Include relevant header files:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "include/dart_api.h"
#include "include/dart_native_api.h"
#include "If_DBRP.h"
Use sample_extension_Init() and ResolveName() to initialize the native extension:
DART_EXPORT Dart_Handle sample_extension_Init(Dart_Handle parent_library) {
if
(Dart_IsError(parent_library)) {
return
parent_library;
}
Dart_Handle result_code =
Dart_SetNativeResolver(parent_library, ResolveName, NULL);
if
(Dart_IsError(result_code)) {
return
result_code;
}
return
Dart_Null();
}
struct
FunctionLookup {
const
char* name;
Dart_NativeFunction function;
};
FunctionLookup function_list[] = {
{"BarcodeReader_ServicePort", barcodeReaderServicePort},
{NULL, NULL}};
Dart_NativeFunction ResolveName(Dart_Handle name,
int
argc,
bool* auto_setup_scope) {
if
(!Dart_IsString(name)) {
return
NULL;
}
Dart_NativeFunction result = NULL;
if
(auto_setup_scope == NULL) {
return
NULL;
}
Dart_EnterScope();
const
char* cname;
HandleError(Dart_StringToCString(name, &cname));
for
(int
i=0; function_list[i].name != NULL; ++i) {
if
(strcmp(function_list[i].name, cname) == 0) {
*auto_setup_scope = true;
result = function_list[i].function;
break;
}
}
if
(result != NULL) {
Dart_ExitScope();
return
result;
}
Dart_ExitScope();
return
result;
}
Read barcode with Dynamsoft C/C++ barcode APIs:
char
**readBarcode(char
*pszFileName, int
*out_len) {
char
**values = NULL;
__int64
llFormat = (OneD | QR_CODE | PDF417 | DATAMATRIX);
char
pszBuffer[512] = { 0 };
int
iMaxCount = MAX_BARCODE_AMOUNT;
int
iIndex = 0;
ReaderOptions ro = { 0 };
int
iRet = -1;
char
* pszTemp = NULL;
unsigned __int64
ullTimeBegin = 0;
unsigned __int64
ullTimeEnd = 0;
size_t
iLen = 0;
FILE* fp = NULL;
int
iExitFlag = 0;
// Set license
CBarcodeReader reader;
reader.InitLicense("38B9B94D8B0E2B41FDE1FB60861C28C0");
// Read barcode
ullTimeBegin = GetTickCount();
ro.llBarcodeFormat = llFormat;
ro.iMaxBarcodesNumPerPage = iMaxCount;
reader.SetReaderOptions(ro);
iRet = reader.DecodeFile(pszFileName);
ullTimeEnd = GetTickCount();
// Output barcode result
pszTemp = (char*)malloc(4096);
if
(iRet != DBR_OK && iRet != DBRERR_LICENSE_EXPIRED && iRet != DBRERR_QR_LICENSE_INVALID &&
iRet != DBRERR_1D_LICENSE_INVALID && iRet != DBRERR_PDF417_LICENSE_INVALID && iRet != DBRERR_DATAMATRIX_LICENSE_INVALID)
{
sprintf_s(pszTemp, 4096, "Failed to read barcode: %s\r\n", DBR_GetErrorString(iRet));
printf(pszTemp);
free(pszTemp);
return
NULL;
}
pBarcodeResultArray paryResult = NULL;
reader.GetBarcodes(&paryResult);
if
(paryResult->iBarcodeCount == 0)
{
sprintf_s(pszTemp, 4096, "No barcode found. Total time spent: %.3f seconds.\r\n", ((float)(ullTimeEnd - ullTimeBegin) / 1000));
printf(pszTemp);
free(pszTemp);
reader.FreeBarcodeResults(&paryResult);
return
NULL;
}
sprintf_s(pszTemp, 4096, "Total barcode(s) found: %d. Total time spent: %.3f seconds\r\n\r\n", paryResult->iBarcodeCount, ((float)(ullTimeEnd - ullTimeBegin) / 1000));
//printf(pszTemp);
values = (char
**)malloc((paryResult->iBarcodeCount + 1) * sizeof(char
*));
for
(iIndex = 0; iIndex < paryResult->iBarcodeCount; iIndex++)
{
char* pszValue = (char*)calloc(paryResult->ppBarcodes[iIndex]->iBarcodeDataLength + 1, sizeof(char));
memcpy(pszValue, paryResult->ppBarcodes[iIndex]->pBarcodeData, paryResult->ppBarcodes[iIndex]->iBarcodeDataLength);
values[iIndex] = pszValue;
}
free(pszTemp);
reader.FreeBarcodeResults(&paryResult);
values[iIndex] = NULL;
*out_len = iIndex;
return
values;
}
Wrap the results into a Dart_CObjects:
void
wrappedBarcodeReader(Dart_Port dest_port_id,
Dart_CObject* message) {
Dart_Port reply_port_id = ILLEGAL_PORT;
if
(message->type == Dart_CObject_kArray &&
2 == message->value.as_array.length) {
// Use .as_array and .as_int32 to access the data in the Dart_CObject.
Dart_CObject* param0 = message->value.as_array.values[0];
Dart_CObject* param1 = message->value.as_array.values[1];
if
(param0->type == Dart_CObject_kString &&
param1->type == Dart_CObject_kSendPort) {
char
* pszFileName = param0->value.as_string;
reply_port_id = param1->value.as_send_port.id;
int
length = 0;
char
**values = readBarcode(pszFileName, &length);
char
**pStart = values;
if
(values != NULL) {
Dart_CObject* results[MAX_BARCODE_AMOUNT];
Dart_CObject collection[MAX_BARCODE_AMOUNT];
int
index = 0;
char
* pszValue = NULL;
while
((pszValue = *pStart) != NULL) {
Dart_CObject value;
value.type = Dart_CObject_kString;
value.value.as_string = pszValue;
collection[index] = value;
++pStart;
++index;
}
for
(int
i = 0; i < length; i++) {
results[i] = &(collection[i]);
}
Dart_CObject message;
message.type = Dart_CObject_kArray;
message.value.as_array.length = length;
message.value.as_array.values = results;
Dart_PostCObject(reply_port_id, &message);
freeString(values);
return;
}
}
}
Dart_CObject result;
result.type = Dart_CObject_kNull;
Dart_PostCObject(reply_port_id, &result);
}
Dart_PostCObject() is the only Dart Embedding API function that should be called from the wrapper or the C function.
Dart_CObjects is defined in dart_native_api.h as follows:
typedef
struct
_Dart_CObject {
Dart_CObject_Type type;
union
{
bool
as_bool;
int32_t as_int32;
int64_t as_int64;
double
as_double;
char* as_string;
struct
{
bool
neg;
intptr_t
used;
struct
_Dart_CObject* digits;
} as_bigint;
struct
{
Dart_Port id;
Dart_Port origin_id;
} as_send_port;
struct
{
int64_t id;
} as_capability;
struct
{
intptr_t
length;
struct
_Dart_CObject** values;
} as_array;
struct
{
Dart_TypedData_Type type;
intptr_t
length;
uint8_t* values;
} as_typed_data;
struct
{
Dart_TypedData_Type type;
intptr_t
length;
uint8_t* data;
void* peer;
Dart_WeakPersistentHandleFinalizer callback;
} as_external_typed_data;
} value;
} Dart_CObject;
Set up the native port:
void
barcodeReaderServicePort(Dart_NativeArguments arguments) {
Dart_EnterScope();
Dart_SetReturnValue(arguments, Dart_Null());
Dart_Port service_port =
Dart_NewNativePort("BarcodeReaderService", wrappedBarcodeReader, true);
if
(service_port != ILLEGAL_PORT) {
Dart_Handle send_port = HandleError(Dart_NewSendPort(service_port));
Dart_SetReturnValue(arguments, send_port);
}
Dart_ExitScope();
}
Build the project to generate sample_extension.dll. Copy sample_asynchronous_extension.dart, sample_extension.dll and DynamsoftBarcodeReaderx64.dll to the same folder:
Create barcode_reader.dart to test the Dart native extension:
import 'sample_asynchronous_extension.dart';
void main() {
BarcodeReader reader = new
BarcodeReader();
reader.readBarcode(r'f:\AllSupportedBarcodeTypes.bmp').then((values) {
if
(values != null) {
for
(var
number in
values) {
print(number);
}
}
});
}