Building Windows Desktop Barcode Reader with Win32 API and Dynamsoft C++ Barcode SDK

Xiao Ling - Nov 28 '23 - - Dev Community

Dynamsoft's official C++ Barcode SDK provides only command line samples in C++, which are not user-friendly for selecting image files and displaying results. The Win32 API (Windows API) is a core set of application programming interfaces primarily used for developing Windows GUI applications in C and C++. This article will guide you through building a Windows desktop barcode reader application with the Win32 API and Dynamsoft C++ Barcode SDK.

Dynamsoft C++ Barcode SDK

  1. Download dynamsoft-barcode-reader-cpp-10.0.20.zip. The SDK includes header files, lib files, and DLL files.
  2. Request a free trial license.

Steps to Build a Windows Desktop Barcode Reader

Let's get started with the Win32 project skeleton in Visual Studio 2022.

Create a Win32 project

Adding Two Panels for Image and Barcode Results

The window can be split into two panels: one for displaying an image and the other for displaying barcode results.

  1. Declare two global variables for the two panels.

    HWND hwndImagePanel = nullptr, hwndResultPanel = nullptr;
    
  2. Create the two panels in the window procedure's WM_CREATE case and assign identifiers to them (e.g., IDC_IMAGE_PANEL and IDC_RESULT_PANEL).

    case WM_CREATE:
    {
        hwndImagePanel = CreateWindowEx(
        0,                              
        L"STATIC",                      
        nullptr,
        WS_CHILD | WS_VISIBLE | SS_ETCHEDFRAME, 
        10, 10, 100, 100,               
        hWnd,                           
        (HMENU)IDC_IMAGE_PANEL,               
        (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),              
        nullptr);
    
        hwndResultPanel = CreateWindowEx(
        0,                              
        L"STATIC",                      
        nullptr,
        WS_CHILD | WS_VISIBLE | SS_ETCHEDFRAME, 
        10, 10, 100, 100,               
        hWnd,                          
        (HMENU)IDC_IMAGE_PANEL,             
        (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),               
        nullptr);
    }
    break;
    
    

Adding a Button for Loading and Displaying an Image File

Implementing a button in a Windows C++ GUI application to load and display an image involves several steps.

  1. Include the necessary headers at the top of your source file.

    #include <windows.h>
    #include <commdlg.h> 
    #include <gdiplus.h>
    using namespace Gdiplus;
    
  2. Declare a global Gdiplus::Image pointer, initialized to nullptr, to load an image file.

    Gdiplus::Image* g_pImage = nullptr;
    
  3. Link the Gdiplus.lib library and initialize GDI+ in the WinMain function.

    #pragma comment (lib, "Gdiplus.lib") 
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
    {
        GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;
        GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
    }
    
  4. Use the CreateWindow function to create a button in the window procedure's WM_CREATE case and assign an identifier to the button (e.g., IDC_LOADIMAGE_BUTTON).

    HWND hwndButton = CreateWindow(
    L"BUTTON",  
    L"Load Image",      
    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,  
    10,         
    10,         
    100,        
    30,         
    hWnd,       
    (HMENU)IDC_LOADIMAGE_BUTTON,       
    (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),
    nullptr);
    
  5. Handle the button click in the window procedure by adding a WM_COMMAND case. Check if the button with the identifier (e.g., IDC_LOADIMAGE_BUTTON) was clicked. On a button click, open an Open File dialog for image file selection.

    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDC_LOADIMAGE_BUTTON:
        {
            OPENFILENAME ofn;       
            TCHAR szFile[260] = { 0 }; 
    
            // Initialize OPENFILENAME structure
            ZeroMemory(&ofn, sizeof(ofn));
            ofn.lStructSize = sizeof(ofn);
            ofn.hwndOwner = hWnd;
            ofn.lpstrFile = szFile;
            ofn.nMaxFile = sizeof(szFile);
            ofn.lpstrFilter = L"All\0*.*\0JPEG\0*.jpg\0PNG\0*.png\0";
            ofn.nFilterIndex = 1;
            ofn.lpstrFileTitle = nullptr;
            ofn.nMaxFileTitle = 0;
            ofn.lpstrInitialDir = nullptr;
            ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
    
            // Open the file dialog
            if (GetOpenFileName(&ofn) == TRUE)
            {
            }
        }
    
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    
  6. Once an image file is selected, invalidate the window to trigger a redraw in the WM_PAINT event, where the image will be displayed.

    if (GetOpenFileName(&ofn) == TRUE)
    {
        delete g_pImage; 
        g_pImage = new Gdiplus::Image(ofn.lpstrFile);
        InvalidateRect(hWnd, nullptr, TRUE);
    }
    
    ...
    
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
    
        if (g_pImage && !g_pImage->GetLastStatus()) 
        {
            // Set your desired width and height
            const int desiredWidth = 800;
            const int desiredHeight = 600;
    
            // Calculate aspect ratio and scaling factor
            float scaleX = static_cast<float>(desiredWidth) / g_pImage->GetWidth();
            float scaleY = static_cast<float>(desiredHeight) / g_pImage->GetHeight();
            float scale = min(scaleX, scaleY); 
    
            // Calculate new dimensions
            int imageWidth = static_cast<int>(scale * g_pImage->GetWidth());
            int imageHeight = static_cast<int>(scale * g_pImage->GetHeight());
    
            Gdiplus::Graphics graphics(hdc);
            graphics.DrawImage(g_pImage, 10, 50, imageWidth, imageHeight);
        }
    
        EndPaint(hWnd, &ps);
    }
    break;
    

Integrating Dynamsoft C++ Barcode SDK into the Win32 Application

To use the Dynamsoft C++ Barcode SDK, you need to include the header files and link the lib files.

  1. Create a dbr folder in your project directory and copy the SDK's header (.h) and library (.lib and .dll) files into it.

    dbr
    ├── include
    ├── lib
    
  2. Include the header file dbr/DynamsoftBarcodeReader.h in your source file.

    #include "dbr/include/DynamsoftCaptureVisionRouter.h"
    using namespace Gdiplus;
    using namespace dynamsoft::license;
    using namespace dynamsoft::cvr;
    using namespace dynamsoft::dbr;
    
  3. Link the lib files by adding them to your project's linker input settings.

    #pragma comment (lib, "dbr/lib/DynamsoftCorex64.lib")
    #pragma comment (lib, "dbr/lib/DynamsoftLicensex64.lib")
    #pragma comment (lib, "dbr/lib/DynamsoftCaptureVisionRouterx64.lib")
    #pragma comment (lib, "dbr/lib/DynamsoftUtilityx64.lib")
    
  4. Right-click the project and navigate to Properties > Build Events > Post-Build Event > Command Line. Here, add a command to copy the SDK's DLL files to your output directory after each build.

    xcopy /Y "$(ProjectDir)\dbr\lib\*.dll" "$(OutDir)"
    

    Post-Build Event

Reading Barcodes from an Image File

In our sample, we will read barcodes from an image file and display the results in a scrollable text box. The steps are as follows:

  1. Initialize the Dynamsoft Barcode Reader in your application's startup routine using a valid license key.

    CCaptureVisionRouter* cvr = nullptr;
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
    {
        ...
        char errorMsgBuffer[512];
        const char* pLicense = "LICENSE-KEY";
        CLicenseManager::InitLicense(pLicense, errorMsgBuffer, 512);
        cvr = new CCaptureVisionRouter;
        ...
    }
    
  2. Create a scrollable text box in the WM_CREATE case of the window procedure and assign it an identifier (e.g., IDC_TEXTBOX).

    HWND hwndEdit = nullptr;
    
    ... 
    
    case WM_CREATE:
    {
        ...
        hwndEdit = CreateWindowEx(
            WS_EX_CLIENTEDGE, 
            L"EDIT",         
            L"",             
            WS_CHILD | WS_VISIBLE | WS_VSCROLL |
            ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
            10, 40, 400, 200,   
            hwndResultPanel,             
            (HMENU)IDC_TEXTBOX, 
            (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),
            nullptr);
    
        if (!hwndEdit)
        {
            MessageBox(hWnd, L"Could not create edit box.", L"Error", MB_OK | MB_ICONERROR);
        }
    }
    break;
    
  3. Use the barcode reader to decode barcodes from the selected image file. Then, display the decoding results in the text box using the SetDlgItemText function.

    if (GetOpenFileName(&ofn) == TRUE)
    {
        delete g_pImage; 
        g_pImage = new Gdiplus::Image(ofn.lpstrFile);
    
        // Decode barcodes from the image
        const char* filename = ConvertWCharToChar(ofn.lpstrFile);
        CCapturedResult* result = cvr->Capture(filename);
        delete filename;
        if (result->GetErrorCode() != 0) {
            printf("%d, %s\n", result->GetErrorCode() , result->GetErrorString() );
        }
        int capturedResultItemCount = result->GetItemsCount();
    
        string content = "No barcode found";
        if (capturedResultItemCount > 0) {
            content = "Total barcode count: " + std::to_string(capturedResultItemCount) + "\r\n\r\n";
    
            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);
                    content += "Result " + std::to_string(j + 1) + ": \r\n";
                    content += "Barcode Format: " + string(barcodeResultItem->GetFormatString()) + "\r\n";
                    content += "Barcode Text: " + string(barcodeResultItem->GetText()) + "\r\n\r\n";
                }
            }
        }
    
        wchar_t* newText = ConvertCharToWChar(content.c_str());
        SetDlgItemText(hwndResultPanel, IDC_TEXTBOX, newText);
        delete newText;
    
        InvalidateRect(hWnd, nullptr, TRUE);
    }
    

Drawing Overlays on the Image

Besides the text content, we can also draw overlays on the image to highlight the barcode locations, which dramatically improves the user experience.

  1. Save the barcode results, including the barcode type and coordinates, in a vector for later use.

    vector<CCapturedResult*> results;
    
    ...
    if (GetOpenFileName(&ofn) == TRUE)
    {
        ...
        results.push_back(result);
        ...
    }
    
  2. In the WM_PAINT case of your window procedure, use the graphics drawing functions to overlay graphics on the image. Utilize the barcode results stored in the vector to accurately position these overlays over the detected barcode locations.

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
    
            if (g_pImage && !g_pImage->GetLastStatus()) 
            {
                ...
    
                // Draw overlay
                if (!results.empty()) {
                    CCapturedResult* result = results[0];
    
                    Gdiplus::Pen pen(Gdiplus::Color(255, 255, 0, 255));
    
                    Gdiplus::FontFamily fontFamily(L"Arial");
                    Gdiplus::Font font(&fontFamily, 24, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
                    Gdiplus::SolidBrush solidBrush(Gdiplus::Color(255, 255, 0, 0));
    
                    int capturedResultItemCount = result->GetItemsCount();
    
                    if (capturedResultItemCount > 0) {
                        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);
    
                                CQuadrilateral location = barcodeResultItem->GetLocation();
                                Gdiplus::Point points[4];
                                points[0].X = location.points[0][0] * scale + 10;
                                points[0].Y = location.points[0][1] * scale + 50;
                                points[1].X = location.points[1][0] * scale + 10;
                                points[1].Y = location.points[1][1] * scale + 50;
                                points[2].X = location.points[2][0] * scale + 10;
                                points[2].Y = location.points[2][1] * scale + 50;
                                points[3].X = location.points[3][0] * scale + 10;
                                points[3].Y = location.points[3][1] * scale + 50;
    
                                graphics.DrawPolygon(&pen, points, 4);
    
                                wchar_t* newText = ConvertCharToWChar(barcodeResultItem->GetText());
                                graphics.DrawString(
                                    newText, -1, &font,
                                    Gdiplus::PointF(points[0].X, points[0].Y),
                                    &solidBrush);
                                delete newText;
    
                            }
                        }
                    }
    
                    results.pop_back();
                    delete result;
                }
            }
    
            EndPaint(hWnd, &ps);
        }
        break;
    

Build and Run the Application

Windows desktop barcode reader

Source Code

https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/10.x/win32_gui

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player