Building Windows Desktop Barcode Reader with Win32 API and Dynamsoft C++ Barcode SDK
Tags: cpp, windows, win32, barcode
Published: 2023-11-28 03:05:05

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](https://www.dynamsoft.com/barcode-reader/downloads/). The SDK includes header files, lib files, and DLL files.
2. Request a [free trial license](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr).

## 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](https://www.dynamsoft.com/codepool/img/2023/11/windows-desktop-application-template.png)

### 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.
    
    ```cpp
    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`).

    ```cpp
    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.
    ```cpp
    #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.
    
    ```cpp
    Gdiplus::Image* g_pImage = nullptr;
    ```

3. Link the `Gdiplus.lib` library and initialize GDI+ in the `WinMain` function.
    
    ```cpp
    #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`).
        
    ```cpp
    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.

    ```cpp
    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.

    ```cpp
    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. 
    ```bash
    dbr
    ├── include
    ├── lib
    ```

2. Include the header file `dbr/DynamsoftBarcodeReader.h` in your source file.
    ```cpp
    #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.
    
    ```cpp
    #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.
    
    ```bash
    xcopy /Y "$(ProjectDir)\dbr\lib\*.dll" "$(OutDir)"
    ```

    ![Post-Build Event](https://www.dynamsoft.com/codepool/img/2023/11/post-build-event.png)

### 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.
    
    ```cpp
    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`).
    
    ```cpp
    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.
    
    ```cpp
    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.
    
    ```cpp
    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.
    ```cpp
    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](https://www.dynamsoft.com/codepool/img/2023/11/windows-win32-cpp-barcode-reader-overlay.png)

## Source Code
[https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/10.x/win32_gui](https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/10.x/win32_gui)