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:Dart include header

Dart lib path

Add dependencies dart.lib and DBRx64.lib:

Dart dependency

Add preprocessor DART_SHARED_LIB:

Dart preprocessor

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:

Dart native extension

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

Dart console barcode reader

Source Code

https://github.com/yushulx/dart-native-extension