Building Windows Desktop Barcode Reader with Win32 API and Dynamsoft C++ Barcode SDK
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.
This article is Part 13 in a 16-Part Series.
- Part 1 - Building a C/C++ Barcode & QR Code Reader for Raspberry Pi with Dynamsoft SDK
- Part 2 - CMake: Build C++ Project for Windows, Linux and macOS
- Part 3 - How to Port Visual Studio C++ Project to Linux with CMake
- Part 4 - Insight Into Dynamsoft Barcode SDK Decoding Performance
- Part 5 - Building ARM64 Barcode and QR Code Scanner on Nvidia Jetson Nano
- Part 6 - How to Decode QR Code on Mac with Apple Silicon
- Part 7 - How to Develop a Desktop GUI Barcode Reader with Qt and C/C++
- Part 8 - How to Build a Desktop Barcode Scanner with Webcam Support Using Qt QCamera
- Part 9 - Building Command-line Barcode and QR Code Reader in C++
- Part 10 - How to Build Linux ARM32 and Aarch64 Barcode QR Scanner in Docker Container
- Part 11 - How to Link MSVC DLLs with MinGW GCC in Windows
- Part 12 - Transforming Raspberry Pi 4 into a Barcode Scanner with a C++ App, USB Camera, and OLED Display
- Part 13 - Building Windows Desktop Barcode Reader with Win32 API and Dynamsoft C++ Barcode SDK
- Part 14 - How to Build a Command-Line Barcode Reader with Rust and C++ Barcode SDK
- Part 15 - How to Decode Barcode and QR Code from WebP Images in C++ and Python
- Part 16 - Building a Desktop C++ Barcode Scanner with Slimmed-Down OpenCV and Webcam
Dynamsoft C++ Barcode SDK
- Download dynamsoft-barcode-reader-cpp-10.0.20.zip. The SDK includes header files, lib files, and DLL files.
- 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.
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.
-
Declare two global variables for the two panels.
HWND hwndImagePanel = nullptr, hwndResultPanel = nullptr;
-
Create the two panels in the window procedure’s
WM_CREATE
case and assign identifiers to them (e.g.,IDC_IMAGE_PANEL
andIDC_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.
- Include the necessary headers at the top of your source file.
#include <windows.h> #include <commdlg.h> #include <gdiplus.h> using namespace Gdiplus;
-
Declare a global
Gdiplus::Image
pointer, initialized tonullptr
, to load an image file.Gdiplus::Image* g_pImage = nullptr;
-
Link the
Gdiplus.lib
library and initialize GDI+ in theWinMain
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); }
-
Use the
CreateWindow
function to create a button in the window procedure’sWM_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);
-
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 anOpen 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;
-
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.
- 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
- 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;
-
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")
-
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)"
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:
-
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; ... }
-
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;
-
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.
-
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); ... }
-
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
Source Code
https://github.com/yushulx/cmake-cpp-barcode-qrcode/tree/main/examples/10.x/win32_gui