Recently, Dynamsoft rolled out C++ Barcode SDK v10.0. This version has been entirely re-written in C++, providing an entirely new suite of APIs for barcode reading. However, if you're using MinGW(Minimalist GNU for Windows) to code with this SDK, such as integrating it into a Qt project, you may encounter linking issues in Windows. To address this challenge, this article will guide you on how to create a shim DLL - a bridging shared library that exports C interfaces, ensuring successful DLL linking with MinGW GCC.
About Dynamsoft C++ Barcode SDK v10.0
- Download the SDK.
- Request a free trial license.
Why Linking DLL Files Fails with MinGW GCC
Dynamsoft's C++ Barcode SDK for Windows is compiled with Microsoft's Visual C++ (MSVC). Due to the differences in ABI (Application Binary Interface), C++ libraries built using the MSVC compiler may not be directly compatible with MinGW. Prior to version 10.0, the Dynamsoft Barcode Reader offered C APIs compatible with MinGW. However, these C APIs were discontinued starting from version 10.0. As a result, when you try to build the HelloWorld sample with MinGW, you'll encounter numerous errors related to undefined references
.
To address this issue, one viable solution is to create a shim DLL by writing bridging code in C, which enables the invocation of Dynamsoft Barcode Reader's C++ APIs.
Creating C Interfaces for Dynamsoft Barcode Reader v10.0
We create a CMake project named bridge
, which contains a bridge.h
file, a bridge.cpp
file, and a CMakeLists.txt
file.
Copy the header and library files of Dynamsoft Barcode Reader v10.0 into the bridging project. The directory structure should look like this:
- bridge
- bridge.h
- bridge.cpp
- CMakeLists.txt
- include
- DynamsoftBarcodeReader.h
- DynamsoftCaptureVisionRouter.h
- DynamsoftCodeParser.h
- DynamsoftCore.h
- DynamsoftDocumentNormalizer.h
- DynamsoftLabelRecognizer.h
- DynamsoftLicense.h
- DynamsoftUtility.h
- platforms
- win
- bin
- DynamicImagex64.dll
- DynamicPdfCorex64.dll
- DynamicPdfx64.dll
- DynamsoftBarcodeReaderx64.dll
- DynamsoftCaptureVisionRouterx64.dll
- DynamsoftCorex64.dll
- DynamsoftImageProcessingx64.dll
- DynamsoftLicensex64.dll
- DynamsoftUtilityx64.dll
- lib
- DynamsoftBarcodeReaderx64.lib
- DynamsoftCaptureVisionRouterx64.lib
- DynamsoftCorex64.lib
- DynamsoftLicensex64.lib
- DynamsoftUtilityx64.lib
Configure the CMakeLists.txt
file for building a shared library:
cmake_minimum_required(VERSION 3.0.0)
project(bridge VERSION 0.1.0)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
link_directories("${PROJECT_SOURCE_DIR}/platforms/win/bin/")
else()
link_directories("${PROJECT_SOURCE_DIR}/platforms/win/lib/")
endif()
INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include/")
add_library(bridge SHARED bridge.cpp)
target_link_libraries (${PROJECT_NAME} "DynamsoftLicensex64" "DynamsoftBarcodeReaderx64" "DynamsoftCaptureVisionRouterx64")
We employ the conditional statement if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
to determine whether the compiler in use is MinGW GCC or MSVC. In the case of MinGW GCC, the DLL files in the bin
folder are linked. Conversely, for MSVC, we link the LIB files in the lib
folder..
We can look to the header files of Dynamsoft Barcode Reader v9.6.20 for guidance in defining the C API in the bridge.h
file. The advantage of this approach is to maintain the API design consistent with that of v9.6.20.
#ifndef C_BRIDGING_H
#define C_BRIDGING_H
#include <iostream>
#include <string>
#include "DynamsoftCaptureVisionRouter.h"
using namespace std;
using namespace dynamsoft::license;
using namespace dynamsoft::cvr;
using namespace dynamsoft::dbr;
#define C_API __declspec(dllexport)
typedef struct
{
CCaptureVisionRouter *cvr;
CCapturedResult *result;
} BarcodeReader;
typedef struct
{
/**Barcode type in BarcodeFormat group 1 as string */
char *barcodeFormatString;
/**The barcode text, ends by '\0' */
char *barcodeText;
/**Reserved memory for the struct. The length of this array indicates the size of the memory reserved for this struct. */
char reserved[44];
} TextResult;
typedef struct
{
/**The total count of text result */
int resultsCount;
/**The text result array */
TextResult *results;
} TextResultArray;
#ifdef __cplusplus
extern "C"
{
#endif
C_API int DBR_InitLicense(const char *pLicense, char errorMsgBuffer[], const int errorMsgBufferLen);
C_API int DBR_DecodeFile(void *barcodeReader, const char *pFileName, const char *pTemplateName);
C_API void *DBR_CreateInstance();
C_API void DBR_DestroyInstance(void *barcodeReader);
C_API const char *DBR_GetVersion();
C_API int DBR_GetAllTextResults(void *barcodeReader, TextResultArray **pResults);
C_API void DBR_FreeTextResults(TextResultArray **pResults);
#ifdef __cplusplus
}
#endif
#endif
In order to provide a C API, we encapsulate the C++ code within extern "C"
blocks. We utilize the __declspec(dllexport)
keyword to export the functions from the DLL.
In the bridge.cpp
file, we can implement the C interfaces as follows:
-
DBR_InitLicense()
: Set the license key for Dynamsoft Barcode Reader.
C_API int DBR_InitLicense(const char *pLicense, char errorMsgBuffer[], const int errorMsgBufferLen) { return CLicenseManager::InitLicense(pLicense, errorMsgBuffer, 512); }
-
DBR_CreateInstance()
: Create an instance ofBarcodeReader
struct that contains aCCaptureVisionRouter
object and aCCapturedResult
object.
C_API void *DBR_CreateInstance() { BarcodeReader *barcodeReader = (BarcodeReader *)calloc(1, sizeof(BarcodeReader)); CCaptureVisionRouter *cvr = new CCaptureVisionRouter; barcodeReader->cvr = cvr; barcodeReader->result = NULL; return (void *)barcodeReader; }
-
DBR_DestroyInstance()
: Destroy theBarcodeReader
instance.
C_API void DBR_DestroyInstance(void *barcodeReader) { if (barcodeReader != NULL) { BarcodeReader *reader = (BarcodeReader *)barcodeReader; if (reader->cvr != NULL) { delete reader->cvr; reader->cvr = NULL; } if (reader->result != NULL) { delete reader->result; reader->result = NULL; } } }
-
DBR_DecodeFile()
: Decode barcodes from an image file.
C_API int DBR_DecodeFile(void *barcodeReader, const char *pFileName, const char *pTemplateName) { BarcodeReader *reader = (BarcodeReader *)barcodeReader; if (!reader || !reader->cvr) return -1; CCapturedResult *result = reader->cvr->Capture(pFileName, CPresetTemplate::PT_READ_BARCODES); int errorCode = result->GetErrorCode(); if (result->GetErrorCode() != 0) { cout << "Error: " << result->GetErrorCode() << "," << result->GetErrorString() << endl; } reader->result = result; return errorCode; }
-
DBR_GetVersion()
: Get the version of Dynamsoft Barcode Reader.
C_API const char *DBR_GetVersion() { return CBarcodeReaderModule::GetVersion(); }
-
DBR_GetAllTextResults()
: Get all text results from theCCapturedResult
object.
C_API int DBR_GetAllTextResults(void *barcodeReader, TextResultArray **pResults) { BarcodeReader *reader = (BarcodeReader *)barcodeReader; if (!reader || !reader->cvr || !reader->result) return -1; CCapturedResult *result = reader->result; int capturedResultItemCount = result->GetCount(); if (capturedResultItemCount == 0) return -1; TextResultArray *textResults = (TextResultArray *)calloc(1, sizeof(TextResultArray)); textResults->resultsCount = capturedResultItemCount; textResults->results = (TextResult *)calloc(capturedResultItemCount, sizeof(TextResult)); *pResults = textResults; for (int j = 0; j < capturedResultItemCount; j++) { const CCapturedResultItem *capturedResultItem = result->GetItem(j); CapturedResultItemType type = capturedResultItem->GetType(); if (type == CapturedResultItemType::CRIT_BARCODE) { const CBarcodeResultItem *barcodeResultItem = dynamic_cast<const CBarcodeResultItem *>(capturedResultItem); char *barcodeFormatString = (char *)barcodeResultItem->GetFormatString(); char *barcodeText = (char *)barcodeResultItem->GetText(); textResults->results[j].barcodeFormatString = (char *)malloc(strlen(barcodeFormatString) + 1); strcpy(textResults->results[j].barcodeFormatString, barcodeFormatString); textResults->results[j].barcodeText = (char *)malloc(strlen(barcodeText) + 1); strcpy(textResults->results[j].barcodeText, barcodeText); } } delete result; reader->result = NULL; return 0; }
-
DBR_FreeTextResults()
: Free the memory allocated for theTextResultArray
object.
C_API void DBR_FreeTextResults(TextResultArray **pResults) { if (pResults) { if (*pResults) { if ((*pResults)->results) { for (int i = 0; i < (*pResults)->resultsCount; i++) { if ((*pResults)->results[i].barcodeFormatString) { free((*pResults)->results[i].barcodeFormatString); } if ((*pResults)->results[i].barcodeText) { free((*pResults)->results[i].barcodeText); } } free((*pResults)->results); } } } }
Compile the shared library using Microsoft Visual C++ through CMake with the following command:
mkdir build
cd build
cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..
cmake --build . --config release
We now have a bridge.dll
file and a bridge.lib
file located in the build/Release
folder. The bridge.lib
file can be used for linkage in MSVC projects, while the bridge.dll
file can be utilized for linkage in MinGW projects.
Establishing DLL Linkage with MinGW GCC
- Create a new CMake project.
- Transfer
bridge.h
,bridge.dll
and the complete Dynamsoft Barcode Reader development package into the target project directory. -
Create a
BarcodeReader.cpp
file. You need to update the license key in theDBR_InitLicense()
function and the image path in theDBR_DecodeFile()
function.
#include "bridge.h" #include <iostream> int main() { std::cout << "Version: " << DBR_GetVersion() << std::endl; char errorMsgBuffer[512]; int errorCode = DBR_InitLicense("LICENSE-KEY", errorMsgBuffer, 512); std::cout << "InitLicense errorCode: " << errorCode << std::endl; void* barcodeReader = DBR_CreateInstance(); errorCode = DBR_DecodeFile(barcodeReader, "../images/UPC-E.jpg", ""); if (errorCode != 0) { std::cout << "Failed to read barcode: " << errorCode << std::endl; DBR_DestroyInstance(barcodeReader); return -1; } TextResultArray *paryResult = NULL; DBR_GetAllTextResults(barcodeReader, &paryResult); std::cout << "Barcode count: " << paryResult->resultsCount << std::endl; for (int index = 0; index < paryResult->resultsCount; index++) { printf("Barcode %d:\n", index + 1); printf(" Type: %s\n", paryResult->results[index].barcodeFormatString); printf(" Text: %s\n", paryResult->results[index].barcodeText); } DBR_FreeTextResults(&paryResult); DBR_DestroyInstance(barcodeReader); return 0; }
-
Configure the
CMakeLists.txt
file for building the executable file:
cmake_minimum_required(VERSION 3.0.0) project(BarcodeReader VERSION 0.1.0) link_directories("${PROJECT_SOURCE_DIR}/") INCLUDE_DIRECTORIES("${PROJECT_SOURCE_DIR}/") add_executable(BarcodeReader BarcodeReader.cpp) target_link_libraries(BarcodeReader "bridge")
-
Build and run the project:
mkdir build cd build cmake -G "MinGW Makefiles" .. cmake --build . --config release .\BarcodeReader.exe
Source Code
https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/dbr10-mingw