Getting Started with Windows API in C

Bosio davide
11 min readJun 20, 2024

--

Introduction

If you’re new to Windows programming in C, this guide will walk you through the essentials. We’ll cover the basics of the Windows API, how to create windows, handle messages, draw graphics, manage user input, and more.

Chapter 1: Introduction to Windows API

What is Windows API?

  • Definition and purpose.
  • History and evolution.

Basic Concepts

  • Difference between Windows API and other APIs.
  • Advantages and use cases.

Setting Up the Environment

  • Installing and configuring a C compiler (like MinGW or Visual Studio).
  • Creating a basic project.

Chapter 2: Windows API Basics

Headers and Libraries

  • Common headers (windows.h, winuser.h, etc.).
  • Linking necessary libraries.

Hello World Program

  • Creating a simple Windows application.
  • Understanding the WinMain function.

Message Loop

  • How Windows processes messages.
  • Example of a basic message loop.

Chapter 3: Window Creation and Management

Creating a Window

  • Registering a window class.
  • Creating a window with CreateWindowEx.
  • Example code for creating a window.

Handling Messages

  • Handling window messages in a Window Procedure.
  • Common messages like WM_PAINT, WM_DESTROY.

MessageBox Example

  • Displaying a simple message box.

Chapter 4: GDI (Graphics Device Interface)

Introduction to GDI

  • What is GDI and its purpose.

Drawing Basics

  • Drawing shapes (lines, rectangles, ellipses).
  • Using device contexts (DCs).

Text Output

  • Drawing text on a window.
  • Font selection and management.

Chapter 5: User Input

Keyboard Input

  • Handling keyboard messages (WM_KEYDOWN, WM_KEYUP).
  • Example code for keyboard handling.

Mouse Input

  • Handling mouse messages (WM_LBUTTONDOWN, WM_MOUSEMOVE).
  • Example code for mouse handling.

Chapter 6: Dialog Boxes and Controls

Standard Dialog Boxes

  • Using common dialogs (open file, save file).

Custom Dialog Boxes

  • Creating and displaying custom dialog boxes.

Controls

  • Using standard controls (buttons, edit boxes, list boxes).
  • Example code for creating and handling controls.

Chapter 7: File I/O

File Handling Functions

  • Using CreateFile, ReadFile, WriteFile.
  • Example code for basic file operations.

File Management

  • Managing file attributes, copying, deleting files.
  • Example code for file management tasks.

Chapter 8: Multithreading and Synchronization

Creating Threads

  • Using CreateThread and thread management functions.
  • Example code for creating and managing threads.

Synchronization

  • Using critical sections, mutexes, and events.
  • Example code for synchronizing threads.

Chapter 9: Networking

Windows Sockets (Winsock)

  • Introduction to Winsock API.
  • Example code for creating a simple client and server.

Network Communication

  • Sending and receiving data over the network.
  • Example code for basic network communication.

Chapter 10: Advanced Topics

Dynamic-Link Libraries (DLLs)

  • Creating and using DLLs.
  • Example code for DLL creation and usage.

Chapter 1: Introduction to Windows API

What is Windows API?

The Windows API, also known as WinAPI, is Microsoft’s core set of application programming interfaces available in the Microsoft Windows operating systems. It allows developers to create applications that can interact with Windows components, providing functionalities such as creating windows, handling input, and managing system resources.

History and Evolution

The Windows API has evolved significantly since its inception. Initially introduced with the early versions of Windows, it has expanded and adapted to accommodate new technologies and features, making it the foundation for Windows programming.

Basic Concepts

  • Windows API vs. Other APIs: Unlike high-level libraries or frameworks, the Windows API provides direct access to the operating system’s core functionalities, offering more control but also requiring a deeper understanding of the system.
  • Advantages and Use Cases: Using the Windows API allows for fine-tuned performance, access to low-level system features, and the ability to create custom solutions not available in higher-level frameworks.

Setting Up the Environment

Before you start coding, you’ll need a suitable development environment.

Installing a C Compiler

For Windows API development, you can use:

  • MinGW: A minimalist GNU for Windows, providing a complete GCC (GNU Compiler Collection) toolchain. link
  • Visual Studio: Microsoft’s integrated development environment (IDE) with powerful debugging and project management features. link

Creating a Basic Project

  1. MinGW:
  • Install MinGW and add it to your system PATH.
  • Create a new directory for your project and create a main.c file.

2. Visual Studio:

  • Install Visual Studio and create a new “Windows Desktop Application” project.
  • Add a new C source file to your project.

Chapter 2: Windows API Basics

Headers and Libraries

To use the Windows API, you need to include specific headers and link against the required libraries.

Common Headers

  • windows.h: The main header file that includes most of the Windows API functions.
  • winuser.h: Contains user interface functions and constants.

Linking Necessary Libraries

When compiling, ensure you’re linking against the correct libraries, such as user32.lib for user interface functions.

Hello World Program

Creating a simple Windows application involves defining the entry point and setting up a message loop.

Understanding the WinMain Function

The WinMain function is the entry point for a graphical Windows-based application.

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Hello, Windows API!", "Hello", MB_OK);
return 0;
}

This program displays a message box with “Hello, Windows API!”.

Message Loop

The message loop processes messages sent to the application.

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
const char CLASS_NAME[] = "Sample Window Class";

WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;

RegisterClass(&wc);

HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);

if (hwnd == NULL) {
return 0;
}

ShowWindow(hwnd, nCmdShow);

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;
}

This code sets up a basic window and message loop.

Chapter 3: Window Creation and Management

Creating a Window

Registering a Window Class

To create a window, you first need to register a window class.

WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;

RegisterClass(&wc);

Creating a Window with CreateWindowEx

After registering the class, you create a window using CreateWindowEx.

HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

Example Code for Creating a Window

Here’s the complete example:

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
const char CLASS_NAME[] = "Sample Window Class";

WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;

RegisterClass(&wc);

HWND hwnd = CreateWindowEx(
0,
CLASS_NAME,
"Learn to Program Windows",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

if (hwnd == NULL) {
return 0;
}

ShowWindow(hwnd, nCmdShow);

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;
}

Handling Messages

Handling Window Messages in a Window Procedure

Messages are handled in the Window Procedure function. For example, WM_PAINT and WM_DESTROY are common messages.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 5, 5, "Hello, Windows API!", 19);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

MessageBox Example

You can use MessageBox to display a simple message box.

MessageBox(hwnd, "Hello, Windows API!", "Message Box", MB_OK);

This displays a message box with the specified text.

Chapter 4: GDI (Graphics Device Interface)

Introduction to GDI

The Graphics Device Interface (GDI) is responsible for representing graphical objects and transmitting them to output devices such as monitors and printers.

Drawing Basics

Drawing Shapes

You can draw shapes using functions like LineTo, Rectangle, and Ellipse.

void DrawShapes(HDC hdc) {
// Draw a line
MoveToEx(hdc, 10, 10, NULL);
LineTo(hdc, 100, 100);

// Draw a rectangle
Rectangle(hdc, 120, 10, 220, 100);

// Draw an ellipse
Ellipse(hdc, 240, 10, 340, 100);
}

Using Device Contexts (DCs)

A device context is a structure that defines a set of graphic objects and their associated attributes, as well as the graphic modes that affect output.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
DrawShapes(hdc);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Text Output

Drawing Text on a Window

You can use TextOut to draw text.

void DrawTextExample(HDC hdc) {
TextOut(hdc, 50, 50, "Hello, GDI!", 10);
}

Font Selection and Management

You can select different fonts using CreateFont and SelectObject.

void DrawTextWithFont(HDC hdc) {
HFONT hFont = CreateFont(24, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, "Arial");
HFONT hOldFont = SelectObject(hdc, hFont);

TextOut(hdc, 50, 50, "Hello, GDI with font!", 20);

SelectObject(hdc, hOldFont);
DeleteObject(hFont);
}

Chapter 5: User Input

Keyboard Input

Handling Keyboard Messages

Keyboard messages include WM_KEYDOWN and WM_KEYUP.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_KEYDOWN:
if (wParam == VK_ESCAPE) {
DestroyWindow(hwnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Mouse Input

Handling Mouse Messages

Mouse messages include WM_LBUTTONDOWN, WM_MOUSEMOVE, etc.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_LBUTTONDOWN:
MessageBox(hwnd, "Left mouse button clicked", "Mouse Click", MB_OK);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Chapter 6: Dialog Boxes and Controls

Standard Dialog Boxes

Using Common Dialogs

Windows provides several standard dialog boxes, such as open and save file dialogs.

OPENFILENAME ofn;
char szFile[260];

ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

if (GetOpenFileName(&ofn) == TRUE) {
MessageBox(hwnd, ofn.lpstrFile, "File Selected", MB_OK);
}

Custom Dialog Boxes

Creating and Displaying Custom Dialog Boxes

You can create custom dialog boxes using resource files and the DialogBox function.

INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
EndDialog(hwndDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}

void ShowCustomDialog(HINSTANCE hInstance, HWND hwnd) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwnd, DialogProc);
}

Controls

Using Standard Controls

Controls include buttons, edit boxes, list boxes, etc.

HWND hButton = CreateWindow(
"BUTTON", // Predefined class; Unicode assumed
"OK", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
10, // x position
10, // y position
100, // Button width
30, // Button height
hwnd, // Parent window
(HMENU) ID_BUTTON_OK, // Control identifier
(HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),
NULL); // Pointer not needed.

Example Code for Creating and Handling Controls

Here’s a complete example:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE: {
CreateWindow(
"BUTTON", "OK",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
10, 10, 100, 30,
hwnd, (HMENU)1, (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE), NULL);
return 0;
}
case WM_COMMAND:
if (LOWORD(wParam) == 1) {
MessageBox(hwnd, "Button clicked", "Notification", MB_OK);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Chapter 7: File I/O

File Handling Functions

Using CreateFile, ReadFile, WriteFile

You can perform file operations using these functions.

HANDLE hFile = CreateFile("example.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
char buffer[100];
DWORD bytesRead;
ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
CloseHandle(hFile);
}

File Management

Managing File Attributes, Copying, Deleting Files

Functions such as GetFileAttributes, CopyFile, and DeleteFile help manage files.

DWORD attributes = GetFileAttributes("example.txt");
if (attributes != INVALID_FILE_ATTRIBUTES) {
// Do something with the attributes
}

CopyFile("example.txt", "copy_of_example.txt", FALSE);
DeleteFile("example.txt");

Example Code for Basic File Operations

Here’s a complete example:

void FileOperationsExample() {
HANDLE hFile = CreateFile("example.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
char buffer[100];
DWORD bytesRead;
ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
CloseHandle(hFile);
}

CopyFile("example.txt", "copy_of_example.txt", FALSE);
DeleteFile("example.txt");
}

Chapter 8: Multithreading and Synchronization

Creating Threads

Using CreateThread and Thread Management Functions

Threads allow concurrent execution within a program.

DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
MessageBox(NULL, "Thread running", "Thread", MB_OK);
return 0;
}

void CreateMyThread() {
HANDLE hThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, NULL);
if (hThread != NULL) {
CloseHandle(hThread);
}
}

Synchronization

Using Critical Sections, Mutexes, and Events

Synchronization objects help manage access to shared resources.

CRITICAL_SECTION cs;

void InitializeCriticalSectionExample() {
InitializeCriticalSection(&cs);
}

void UseCriticalSection() {
EnterCriticalSection(&cs);
// Access shared resource
LeaveCriticalSection(&cs);
}

void DeleteCriticalSectionExample() {
DeleteCriticalSection(&cs);
}

Example Code for Synchronizing Threads

Here’s a complete example:

CRITICAL_SECTION cs;

DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
EnterCriticalSection(&cs);
MessageBox(NULL, "Thread running", "Thread", MB_OK);
LeaveCriticalSection(&cs);
return 0;
}

void CreateMyThread() {
InitializeCriticalSection(&cs);

HANDLE hThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, NULL);
if (hThread != NULL) {
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}

DeleteCriticalSection(&cs);
}

Chapter 9: Networking

Windows Sockets (Winsock)

Introduction to Winsock API

The Winsock API allows for network communication.

Example Code for Creating a Simple Client and Server

Simple TCP Client

#include <winsock2.h>

int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_family = AF_INET;
server.sin_port = htons(80);

connect(sock, (struct sockaddr*)&server, sizeof(server));

char *message = "GET / HTTP/1.1\r\n\r\n";
send(sock, message, strlen(message), 0);

closesocket(sock);
WSACleanup();

return 0;
}

Simple TCP Server

#include <winsock2.h>

int main() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

SOCKET server_sock = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in server, client;
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(80);

bind(server_sock, (struct sockaddr*)&server, sizeof(server));
listen(server_sock, 3);

int client_size = sizeof(struct sockaddr_in);
SOCKET client_sock = accept(server_sock, (struct sockaddr*)&client, &client_size);

char *message = "Hello Client\r\n";
send(client_sock, message, strlen(message), 0);

closesocket(client_sock);
closesocket(server_sock);
WSACleanup();

return 0;
}

Network Communication

Sending and Receiving Data Over the Network

You can use send and recv functions for data transmission.

char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0);

Example Code for Basic Network Communication

Here’s a complete example:

void NetworkCommunicationExample() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in server;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_family = AF_INET;
server.sin_port = htons(80);

connect(sock, (struct sockaddr*)&server, sizeof(server));

char *message = "GET / HTTP/1.1\r\n\r\n";
send(sock, message, strlen(message), 0);

char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0);

closesocket(sock);
WSACleanup();
}

Chapter 10: Advanced Topics

Dynamic-Link Libraries (DLLs)

Creating and Using DLLs

DLLs allow code to be modular and reusable.

// DLL Main
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

// Exported function
__declspec(dllexport) void HelloWorld() {
MessageBox(NULL, "Hello from DLL!", "Hello", MB_OK);
}

Using the DLL

typedef void (*HelloWorldFunc)();

void LoadAndUseDLL() {
HMODULE hLib = LoadLibrary("example.dll");
if (hLib != NULL) {
HelloWorldFunc helloWorld = (HelloWorldFunc)GetProcAddress(hLib, "HelloWorld");
if (helloWorld != NULL) {
helloWorld();
}
FreeLibrary(hLib);
}
}

COM Programming

Introduction to Component Object Model (COM)

COM is a platform-independent, distributed, object-oriented system for creating binary software components.

Example Code for Basic COM Client and Server

COM Server

#include <windows.h>
#include <unknwn.h>

// Sample COM interface
interface ISample : IUnknown {
virtual HRESULT STDMETHODCALLTYPE DoSomething() = 0;
};

// Sample COM class
class Sample : public ISample {
public:
Sample() : refCount(1) {}

// IUnknown methods
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
if (riid == IID_IUnknown || riid == __uuidof(ISample)) {
*ppvObject = static_cast<ISample*>(this);
AddRef();
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}

ULONG STDMETHODCALLTYPE AddRef() override {
return InterlockedIncrement(&refCount);
}

ULONG STDMETHODCALLTYPE Release() override {
ULONG count = InterlockedDecrement(&refCount);
if (count == 0) {
delete this;
}
return count;
}

// ISample method
HRESULT STDMETHODCALLTYPE DoSomething() override {
MessageBox(NULL, "Hello from COM!", "COM", MB_OK);
return S_OK;
}

private:
LONG refCount;
};

COM Client

#include <windows.h>
#include <objbase.h>
#include "Sample.h"

int main() {
CoInitialize(NULL);

ISample* pSample;
HRESULT hr = CoCreateInstance(CLSID_Sample, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSample));
if (SUCCEEDED(hr)) {
pSample->DoSomething();
pSample->Release();
}

CoUninitialize();
return 0;
}

Error Handling and Debugging

Using GetLastError and FormatMessage

These functions help retrieve and format error messages.

DWORD error = GetLastError();
char buffer[256];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, 0, buffer, sizeof(buffer), NULL);
MessageBox(NULL, buffer, "Error", MB_OK);

--

--