How to Link MSVC DLLs with MinGW GCC in Windows

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

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.

dbr10 mingw error

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 of BarcodeReader struct that contains a CCaptureVisionRouter object and a CCapturedResult 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 the BarcodeReader 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 the CCapturedResult 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 the TextResultArray 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

  1. Create a new CMake project.
  2. Transfer bridge.h, bridge.dll and the complete Dynamsoft Barcode Reader development package into the target project directory.
  3. Create a BarcodeReader.cpp file. You need to update the license key in the DBR_InitLicense() function and the image path in the DBR_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;
     }
    
  4. 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")
    
  5. Build and run the project:

     mkdir build
     cd build
     cmake -G "MinGW Makefiles" ..
     cmake --build . --config release
     .\BarcodeReader.exe
    

    link DLL via MinGW gcc

Source Code

https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/dbr10-mingw