How to Link MSVC-Compiled DLLs with MinGW GCC Using a C Shim DLL

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.

What you’ll build: A C shim DLL compiled with MSVC that wraps Dynamsoft Barcode Reader v10.0 C++ APIs behind extern "C" interfaces, enabling MinGW GCC projects to link and call the SDK without ABI conflicts.

Key Takeaways

  • MinGW GCC cannot directly link MSVC-compiled C++ DLLs due to ABI incompatibility; a C shim DLL solves this.
  • The shim DLL uses extern "C" and __declspec(dllexport) to expose C functions that wrap the Dynamsoft Capture Vision C++ API.
  • MinGW links against the .dll file directly, while MSVC links against the .lib import library — the CMake config handles both with a compiler ID check.
  • This pattern applies to any MSVC-built C++ library you need to consume from MinGW, not just Dynamsoft SDKs.

Common Developer Questions

  • How do I fix “undefined reference” errors when linking an MSVC DLL with MinGW GCC?
  • Can MinGW GCC link against MSVC-compiled C++ libraries on Windows?
  • How do I create a C wrapper shim DLL to bridge MSVC and MinGW toolchains?

Prerequisites

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.

Step 1: Create C Interfaces for the Barcode SDK

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.

  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

Common Issues and Edge Cases

  • “undefined reference to __imp_...” errors: This means MinGW is trying to link C++ mangled symbols. Ensure your shim DLL header wraps all function declarations in extern "C" blocks and that you are linking the .dll file (not the .lib) from MinGW.
  • Missing DLL at runtime: Even if linking succeeds, bridge.dll and all Dynamsoft runtime DLLs (e.g., DynamsoftCorex64.dll, DynamsoftLicensex64.dll) must be in the same directory as the executable or on the system PATH.
  • 32-bit vs 64-bit mismatch: Both the shim DLL and the MinGW project must target the same architecture. If you compile the shim with MSVC x64, use MinGW 64-bit (x86_64-w64-mingw32-g++), not the 32-bit variant.

Source Code

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