Написать программу на c под windows

Компилятор GCC. Первая программа на языке Си на Windows, набор Mingw-w64 и MSYS2 его установка

Последнее обновление: 01.01.2023

Установка компилятора

Рассмотрим создание первой простейшей программы на языке Си с помощью компилятора GCC, который на сегодняшний день является одим из
наиболее популярных компиляторов для Cи и который доступен для разных платформ. Более подобному информацию о GCC можно получить на официальном сайте проекта https://gcc.gnu.org/.

Набор компиляторов GCC распространяется в различных версиях. Для Windows одной из наиболее популярных версий является пакет средств для разработки от
некоммерческого проекта MSYS2. Следует отметить, что для MSYS2 требуется 64-битная версия Windows 7 и выше (то есть Vista, XP и более ранние версии не подходят)

Итак, загрузим программу установки MSYS2 с официального сайта MSYS2:

Установка MSYS для разработки под С

После загрузки запустим программу установки:

Установка пакета mingw-w64 и msys2 на Windows

На первом шаге установки будет предложено установить каталог для установки. По умолчанию это каталог C:msys64:

Установка компиляторов Си MSYS2 на Windows

Оставим каталог установки по умолчанию (при желании можно изменить). На следующем шаге устанавливаются настройки для ярлыка для меню Пуск, и затем собственно будет произведена установка.
После завершения установки нам отобразить финальное окно, в котором нажмем на кнопку Завершить

Установка компиляторов MSYS2 на Windows

После завершения установки запустится консольное приложение MSYS2.exe. Если по каким-то причинам оно не запустилось,
то в папке установки C:/msys64 надо найти файл usrt_64.exe:

компиляторы MSYS2.exe на Windows

Теперь нам надо установить собственно набор компиляторов GCC. Для этого введем в этом приложении следующую команду:

pacman -S mingw-w64-ucrt-x86_64-gcc

Для управления пакетами MSYS2 использует пакетный менеджер Packman. И данная команда говорит пакетному менелжеру packman установить пакет mingw-w64-ucrt-x86_64-gcc,
который представляет набор компиляторов GCC (название устанавливаемого пакета указывается после параметра -S).

Установка компиляторов MSYS2 на Windows

и после завершения установки мы можем приступать к программированию на языке Си. Если мы откроем каталог установки и зайдем в нем в папку C:msys64ucrt64bin,
то найдем там все необходимые файлы компиляторов:

Компилятор GCC на Windows

В частности, файл gcc.exe как раз и будет представлять компилятор для языка Си.

Далее для упрощения запуска компилятора мы можем добавить путь к нему в Переменные среды. Для этого можно в окне поиска в Windows ввести «изменение переменных среды текущего пользователя»:

изменение переменных среды текущего пользователя в Windows

Нам откроется окно Переменныех среды:

Добавление GCC в переменные среды на Windows

И добавим путь к компилятору C:msys64ucrt64bin:

Определение пути к компилятору GCC в переменных среды на Windows

Чтобы убедиться, что набор компиляторов GCC успешно установлен, введем следующую команду:

В этом случае нам должна отобразиться версия компиляторов

Версия компиляторов MSYS2 GCC на Windows

Создание первой программы

Итак, компилятор установлен, и теперь мы можем написать первую программу. Для этого потребуется любой текстовый редактор для набора исходного кода.
Можно взять распространенный редактор Visual Studio Code или даже обычный встроенный Блокнот.

Итак, создадим на жестком диске папку для исходных файлов. А в этой папке создадим новый файл, который назовем hello.c.

Первая программа на Си в Windows

В моем случае файл hello.c находится в папке C:c.

Теперь определим в файле hello.c простейший код, который будет выводить строку на консоль:

#include <stdio.h>			// подключаем заголовочный файл stdio.h
int main(void)						// определяем функцию main
{									// начало функции
	printf("Hello METANIT.COM!n");	// выводим строку на консоль
	return 0;						// выходим из функции
}									// конец функции

Для вывода строки на консоль необходимо подключить нужный функционал. Для этого в начале файла идет строка

#include <stdio.h>

Директива include подключает заголовочный файл stdio.h, который содержит определение функции printf, которая нужна для вывода строки на консоль.

Далее идет определение функции int main(void). Функция main должна присутствовать в любой программе на Си, с нее собственно и начинается
выполнение приложения.

Ключевое слово int в определении функции int main(void) говорит о том, что функция возвращает целое число.
А слово void в скобках указывает, что функция не принимает параметров.

Тело функции main заключено в фигурные скобки {}. В теле функции происходит вывод строки на консоль с помощью функции printf, в которую передается выводимая строка «Hello METANIT.COM!».

В конце осуществляем выход из функции с помощью оператора return. Так как функция должна возвращать целое число, то после return указывается число 0.
Ноль используется в качестве индикатора успешного завершения программы.

После каждого действия в функции ставятся точка с запятой.

Язык программирования Си в Visual Studio Code

Теперь скомпилируем этот файл. Для этого откроем командную строку Windows и вначале с помощью команды cd перейдем к папке с исходным файлом:

Чтобы скомпилировать исходный код, необходимо компилятору gcc передать в качестве параметра файл hello.c:

После этого будет скомпилирован исполняемый файл, который в Windows по умолчанию называется a.exe. И мы можем обратиться к этому файлу

и в этом случае консоль выведет строку «Hello METANIT.COM!», собственно как и прописано в коде.

Стоит отметить, что мы можем переопределить имя компилируемого файла с помощью флага -o и передав ему имя файла, в который будет компилироваться программа.
Например:

В этом случае программа будет компилироваться в файл hello.exe, который мы также запустить.

Запуск компилятора GCC на Windows

Чтобы не приходилось вводить две команды: одну для компиляции программы и другую для ее запуска, мы можем объединить команды:

gcc hello.c -o hello.exe && hello

Эта команда сначала компилирует код в файл hello.exe, а потом сразу запускает его.

title description ms.custom ms.date helpviewer_keywords

Walkthrough: Create a traditional Windows Desktop application (C++)

How to create a minimal, traditional Windows Desktop application using Visual Studio, C++, and the Win32 API

get-started-article

10/27/2021

Windows applications [C++], Win32

Windows Desktop applications [C++]

Windows API [C++]

Walkthrough: Create a traditional Windows Desktop application (C++)

This walkthrough shows how to create a traditional Windows desktop application in Visual Studio. The example application you’ll create uses the Windows API to display «Hello, Windows desktop!» in a window. You can use the code that you develop in this walkthrough as a pattern to create other Windows desktop applications.

The Windows API (also known as the Win32 API, Windows Desktop API, and Windows Classic API) is a C-language-based framework for creating Windows applications. It has been in existence since the 1980s and has been used to create Windows applications for decades. More advanced and easier-to-program frameworks have been built on top of the Windows API. For example, MFC, ATL, the .NET frameworks. Even the most modern Windows Runtime code for UWP and Store apps written in C++/WinRT uses the Windows API underneath. For more information about the Windows API, see Windows API Index. There are many ways to create Windows applications, but the process above was the first.

[!IMPORTANT]
For the sake of brevity, some code statements are omitted in the text. The Build the code section at the end of this document shows the complete code.

Prerequisites

  • A computer that runs Microsoft Windows 7 or later versions. We recommend Windows 10 or later for the best development experience.

  • A copy of Visual Studio. For information on how to download and install Visual Studio, see Install Visual Studio. When you run the installer, make sure that the Desktop development with C++ workload is checked. Don’t worry if you didn’t install this workload when you installed Visual Studio. You can run the installer again and install it now.

    Detail of the Desktop development with C++ workload in the Visual Studio Installer.

  • An understanding of the basics of using the Visual Studio IDE. If you’ve used Windows desktop apps before, you can probably keep up. For an introduction, see Visual Studio IDE feature tour.

  • An understanding of enough of the fundamentals of the C++ language to follow along. Don’t worry, we don’t do anything too complicated.

Create a Windows desktop project

Follow these steps to create your first Windows desktop project. As you go, you’ll enter the code for a working Windows desktop application. To see the documentation for your preferred version of Visual Studio, use the Version selector control. It’s found at the top of the table of contents on this page.

::: moniker range=»>=msvc-160″

To create a Windows desktop project in Visual Studio

  1. From the main menu, choose File > New > Project to open the Create a New Project dialog box.

  2. At the top of the dialog, set Language to C++, set Platform to Windows, and set Project type to Desktop.

  3. From the filtered list of project types, choose Windows Desktop Wizard then choose Next. In the next page, enter a name for the project, for example, DesktopApp.

  4. Choose the Create button to create the project.

  5. The Windows Desktop Project dialog now appears. Under Application type, select Desktop application (.exe). Under Additional options, select Empty project. Choose OK to create the project.

  6. In Solution Explorer, right-click the DesktopApp project, choose Add, and then choose New Item.

    Short video showing the user adding a new item to DesktopApp Project in Visual Studio 2019.

  7. In the Add New Item dialog box, select C++ File (.cpp). In the Name box, type a name for the file, for example, HelloWindowsDesktop.cpp. Choose Add.

    Screenshot of the Add New Item dialog box in Visual Studio 2019 with Installed > Visual C plus plus selected and the C plus plus File option highlighted.

Your project is now created and your source file is opened in the editor. To continue, skip ahead to Create the code.

::: moniker-end

::: moniker range=»msvc-150″

To create a Windows desktop project in Visual Studio 2017

  1. On the File menu, choose New and then choose Project.

  2. In the New Project dialog box, in the left pane, expand Installed > Visual C++, then select Windows Desktop. In the middle pane, select Windows Desktop Wizard.

    In the Name box, type a name for the project, for example, DesktopApp. Choose OK.

    Screenshot of the New Project dialog box in Visual Studio 2017 with Installed > Visual C plus plus > Windows Desktop selected, the Windows Desktop Wizard option highlighted, and DesktopApp typed in the Name text box.

  3. In the Windows Desktop Project dialog, under Application type, select Windows application (.exe). Under Additional options, select Empty project. Make sure Precompiled Header isn’t selected. Choose OK to create the project.

  4. In Solution Explorer, right-click the DesktopApp project, choose Add, and then choose New Item.

    Short video showing the user adding a new item to DesktopApp Project in Visual Studio 2017.

  5. In the Add New Item dialog box, select C++ File (.cpp). In the Name box, type a name for the file, for example, HelloWindowsDesktop.cpp. Choose Add.

    Screenshot of the Add New Item dialog box in Visual Studio 2017 with Installed > Visual C plus plus selected and the C plus plus File option highlighted.

Your project is now created and your source file is opened in the editor. To continue, skip ahead to Create the code.

::: moniker-end

::: moniker range=»msvc-140″

To create a Windows desktop project in Visual Studio 2015

  1. On the File menu, choose New and then choose Project.

  2. In the New Project dialog box, in the left pane, expand Installed > Templates > Visual C++, and then select Win32. In the middle pane, select Win32 Project.

    In the Name box, type a name for the project, for example, DesktopApp. Choose OK.

    Screenshot of the New Project dialog box in Visual Studio 2015 with Installed > Templates > Visual C plus plus > Win32 selected, the Win32 Project option highlighted, and DesktopApp typed in the Name text box.

  3. On the Overview page of the Win32 Application Wizard, choose Next.

    Create DesktopApp in Win32 Application Wizard Overview page.

  4. On the Application Settings page, under Application type, select Windows application. Under Additional options, uncheck Precompiled header, then select Empty project. Choose Finish to create the project.

  5. In Solution Explorer, right-click the DesktopApp project, choose Add, and then choose New Item.

    Short video showing the user adding a new item to DesktopApp Project in Visual Studio 2015.

  6. In the Add New Item dialog box, select C++ File (.cpp). In the Name box, type a name for the file, for example, HelloWindowsDesktop.cpp. Choose Add.

    Screenshot of the Add New Item dialog box in Visual Studio 2015 with Installed > Visual C plus plus selected and the C plus plus File option highlighted.

Your project is now created and your source file is opened in the editor.

::: moniker-end

Create the code

Next, you’ll learn how to create the code for a Windows desktop application in Visual Studio.

To start a Windows desktop application

  1. Just as every C application and C++ application must have a main function as its starting point, every Windows desktop application must have a WinMain function. WinMain has the following syntax.

    int WINAPI WinMain(
       _In_ HINSTANCE hInstance,
       _In_opt_ HINSTANCE hPrevInstance,
       _In_ LPSTR     lpCmdLine,
       _In_ int       nCmdShow
    );

    For information about the parameters and return value of this function, see WinMain entry point.

    [!NOTE]
    What are all those extra words, such as WINAPI, or CALLBACK, or HINSTANCE, or _In_? The traditional Windows API uses typedefs and preprocessor macros extensively to abstract away some of the details of types and platform-specific code, such as calling conventions, __declspec declarations, and compiler pragmas. In Visual Studio, you can use the IntelliSense Quick Info feature to see what these typedefs and macros define. Hover your mouse over the word of interest, or select it and press Ctrl+K, Ctrl+I for a small pop-up window that contains the definition. For more information, see Using IntelliSense. Parameters and return types often use SAL Annotations to help you catch programming errors. For more information, see Using SAL Annotations to Reduce C/C++ Code Defects.

  2. Windows desktop programs require <windows.h>. <tchar.h> defines the TCHAR macro, which resolves ultimately to wchar_t if the UNICODE symbol is defined in your project, otherwise it resolves to char. If you always build with UNICODE enabled, you don’t need TCHAR and can just use wchar_t directly.

    #include <windows.h>
    #include <tchar.h>
  3. Along with the WinMain function, every Windows desktop application must also have a window-procedure function. This function is typically named WndProc, but you can name it whatever you like. WndProc has the following syntax.

    LRESULT CALLBACK WndProc(
       _In_ HWND   hWnd,
       _In_ UINT   message,
       _In_ WPARAM wParam,
       _In_ LPARAM lParam
    );

    In this function, you write code to handle messages that the application receives from Windows when events occur. For example, if a user chooses an OK button in your application, Windows will send a message to you and you can write code inside your WndProc function that does whatever work is appropriate. It’s called handling an event. You only handle the events that are relevant for your application.

    For more information, see Window Procedures.

To add functionality to the WinMain function

  1. In the WinMain function, you populate a structure of type WNDCLASSEX. The structure contains information about the window: the application icon, the background color of the window, the name to display in the title bar, among other things. Importantly, it contains a function pointer to your window procedure. The following example shows a typical WNDCLASSEX structure.

    WNDCLASSEX wcex;
    
    wcex.cbSize         = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(wcex.hInstance, IDI_APPLICATION);
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = NULL;
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, IDI_APPLICATION);

    For information about the fields of the structure above, see WNDCLASSEX.

  2. Register the WNDCLASSEX with Windows so that it knows about your window and how to send messages to it. Use the RegisterClassEx function and pass the window class structure as an argument. The _T macro is used because we use the TCHAR type.

    if (!RegisterClassEx(&wcex))
    {
       MessageBox(NULL,
          _T("Call to RegisterClassEx failed!"),
          _T("Windows Desktop Guided Tour"),
          NULL);
    
       return 1;
    }
  3. Now you can create a window. Use the CreateWindowEx function.

    static TCHAR szWindowClass[] = _T("DesktopApp");
    static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application");
    
    // The parameters to CreateWindowEx explained:
    // WS_EX_OVERLAPPEDWINDOW : An optional extended window style.
    // szWindowClass: the name of the application
    // szTitle: the text that appears in the title bar
    // WS_OVERLAPPEDWINDOW: the type of window to create
    // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y)
    // 500, 100: initial size (width, length)
    // NULL: the parent of this window
    // NULL: this application does not have a menu bar
    // hInstance: the first parameter from WinMain
    // NULL: not used in this application
    HWND hWnd = CreateWindowEx(
    WS_EX_OVERLAPPEDWINDOW,
       szWindowClass,
       szTitle,
       WS_OVERLAPPEDWINDOW,
       CW_USEDEFAULT, CW_USEDEFAULT,
       500, 100,
       NULL,
       NULL,
       hInstance,
       NULL
    );
    if (!hWnd)
    {
       MessageBox(NULL,
          _T("Call to CreateWindowEx failed!"),
          _T("Windows Desktop Guided Tour"),
          NULL);
    
       return 1;
    }

    This function returns an HWND, which is a handle to a window. A handle is somewhat like a pointer that Windows uses to keep track of open windows. For more information, see Windows Data Types.

  4. At this point, the window has been created, but we still need to tell Windows to make it visible. That’s what this code does:

    // The parameters to ShowWindow explained:
    // hWnd: the value returned from CreateWindow
    // nCmdShow: the fourth parameter from WinMain
    ShowWindow(hWnd,
       nCmdShow);
    UpdateWindow(hWnd);

    The displayed window doesn’t have much content because you haven’t yet implemented the WndProc function. In other words, the application isn’t yet handling the messages that Windows is now sending to it.

  5. To handle the messages, we first add a message loop to listen for the messages that Windows sends. When the application receives a message, this loop dispatches it to your WndProc function to be handled. The message loop resembles the following code.

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
       TranslateMessage(&msg);
       DispatchMessage(&msg);
    }
    
    return (int) msg.wParam;

    For more information about the structures and functions in the message loop, see MSG, GetMessage, TranslateMessage, and DispatchMessage.

    At this point, the WinMain function should resemble the following code.

    int WINAPI WinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPSTR lpCmdLine,
                       int nCmdShow)
    {
       WNDCLASSEX wcex;
    
       wcex.cbSize = sizeof(WNDCLASSEX);
       wcex.style          = CS_HREDRAW | CS_VREDRAW;
       wcex.lpfnWndProc    = WndProc;
       wcex.cbClsExtra     = 0;
       wcex.cbWndExtra     = 0;
       wcex.hInstance      = hInstance;
       wcex.hIcon          = LoadIcon(wcex.hInstance, IDI_APPLICATION);
       wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
       wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
       wcex.lpszMenuName   = NULL;
       wcex.lpszClassName  = szWindowClass;
       wcex.hIconSm        = LoadIcon(wcex.hInstance, IDI_APPLICATION);
    
       if (!RegisterClassEx(&wcex))
       {
          MessageBox(NULL,
             _T("Call to RegisterClassEx failed!"),
             _T("Windows Desktop Guided Tour"),
             NULL);
    
          return 1;
       }
    
       // Store instance handle in our global variable
       hInst = hInstance;
    
       // The parameters to CreateWindowEx explained:
       // WS_EX_OVERLAPPEDWINDOW : An optional extended window style.
       // szWindowClass: the name of the application
       // szTitle: the text that appears in the title bar
       // WS_OVERLAPPEDWINDOW: the type of window to create
       // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y)
       // 500, 100: initial size (width, length)
       // NULL: the parent of this window
       // NULL: this application dows not have a menu bar
       // hInstance: the first parameter from WinMain
       // NULL: not used in this application
       HWND hWnd = CreateWindowEx(
          WS_EX_OVERLAPPEDWINDOW,
          szWindowClass,
          szTitle,
          WS_OVERLAPPEDWINDOW,
          CW_USEDEFAULT, CW_USEDEFAULT,
          500, 100,
          NULL,
          NULL,
          hInstance,
          NULL
       );
    
       if (!hWnd)
       {
          MessageBox(NULL,
             _T("Call to CreateWindow failed!"),
             _T("Windows Desktop Guided Tour"),
             NULL);
    
          return 1;
       }
    
       // The parameters to ShowWindow explained:
       // hWnd: the value returned from CreateWindow
       // nCmdShow: the fourth parameter from WinMain
       ShowWindow(hWnd,
          nCmdShow);
       UpdateWindow(hWnd);
    
       // Main message loop:
       MSG msg;
       while (GetMessage(&msg, NULL, 0, 0))
       {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
       }
    
       return (int) msg.wParam;
    }

To add functionality to the WndProc function

  1. To enable the WndProc function to handle the messages that the application receives, implement a switch statement.

    One important message to handle is the WM_PAINT message. The application receives the WM_PAINT message when part of its displayed window must be updated. The event can occur when a user moves a window in front of your window, then moves it away again. Your application doesn’t know when these events occur. Only Windows knows, so it notifies your app with a WM_PAINT message. When the window is first displayed, all of it must be updated.

    To handle a WM_PAINT message, first call BeginPaint, then handle all the logic to lay out the text, buttons, and other controls in the window, and then call EndPaint. For the application, the logic between the beginning call and the ending call displays the string «Hello, Windows desktop!» in the window. In the following code, the TextOut function is used to display the string.

    PAINTSTRUCT ps;
    HDC hdc;
    TCHAR greeting[] = _T("Hello, Windows desktop!");
    
    switch (message)
    {
    case WM_PAINT:
       hdc = BeginPaint(hWnd, &ps);
    
       // Here your application is laid out.
       // For this introduction, we just print out "Hello, Windows desktop!"
       // in the top left corner.
       TextOut(hdc,
          5, 5,
          greeting, _tcslen(greeting));
       // End application-specific layout section.
    
       EndPaint(hWnd, &ps);
       break;
    }

    HDC in the code is a handle to a device context, which is used to draw in the window’s client area. Use the BeginPaint and EndPaint functions to prepare for and complete the drawing in the client area. BeginPaint returns a handle to the display device context used for drawing in the client area; EndPaint ends the paint request and releases the device context.

  2. An application typically handles many other messages. For example, WM_CREATE when a window is first created, and WM_DESTROY when the window is closed. The following code shows a basic but complete WndProc function.

    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
       PAINTSTRUCT ps;
       HDC hdc;
       TCHAR greeting[] = _T("Hello, Windows desktop!");
    
       switch (message)
       {
       case WM_PAINT:
          hdc = BeginPaint(hWnd, &ps);
    
          // Here your application is laid out.
          // For this introduction, we just print out "Hello, Windows desktop!"
          // in the top left corner.
          TextOut(hdc,
             5, 5,
             greeting, _tcslen(greeting));
          // End application specific layout section.
    
          EndPaint(hWnd, &ps);
          break;
       case WM_DESTROY:
          PostQuitMessage(0);
          break;
       default:
          return DefWindowProc(hWnd, message, wParam, lParam);
          break;
       }
    
       return 0;
    }

Build the code

As promised, here’s the complete code for the working application.

To build this example

  1. Delete any code you’ve entered in HelloWindowsDesktop.cpp in the editor. Copy this example code and then paste it into HelloWindowsDesktop.cpp:

    // HelloWindowsDesktop.cpp
    // compile with: /D_UNICODE /DUNICODE /DWIN32 /D_WINDOWS /c
    
    #include <windows.h>
    #include <stdlib.h>
    #include <string.h>
    #include <tchar.h>
    
    // Global variables
    
    // The main window class name.
    static TCHAR szWindowClass[] = _T("DesktopApp");
    
    // The string that appears in the application's title bar.
    static TCHAR szTitle[] = _T("Windows Desktop Guided Tour Application");
    
    // Stored instance handle for use in Win32 API calls such as FindResource
    HINSTANCE hInst;
    
    // Forward declarations of functions included in this code module:
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    
    int WINAPI WinMain(
       _In_ HINSTANCE hInstance,
       _In_opt_ HINSTANCE hPrevInstance,
       _In_ LPSTR     lpCmdLine,
       _In_ int       nCmdShow
    )
    {
       WNDCLASSEX wcex;
    
       wcex.cbSize = sizeof(WNDCLASSEX);
       wcex.style          = CS_HREDRAW | CS_VREDRAW;
       wcex.lpfnWndProc    = WndProc;
       wcex.cbClsExtra     = 0;
       wcex.cbWndExtra     = 0;
       wcex.hInstance      = hInstance;
       wcex.hIcon          = LoadIcon(wcex.hInstance, IDI_APPLICATION);
       wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
       wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
       wcex.lpszMenuName   = NULL;
       wcex.lpszClassName  = szWindowClass;
       wcex.hIconSm        = LoadIcon(wcex.hInstance, IDI_APPLICATION);
    
       if (!RegisterClassEx(&wcex))
       {
          MessageBox(NULL,
             _T("Call to RegisterClassEx failed!"),
             _T("Windows Desktop Guided Tour"),
             NULL);
    
          return 1;
       }
    
       // Store instance handle in our global variable
       hInst = hInstance;
    
       // The parameters to CreateWindowEx explained:
       // WS_EX_OVERLAPPEDWINDOW : An optional extended window style.
       // szWindowClass: the name of the application
       // szTitle: the text that appears in the title bar
       // WS_OVERLAPPEDWINDOW: the type of window to create
       // CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y)
       // 500, 100: initial size (width, length)
       // NULL: the parent of this window
       // NULL: this application does not have a menu bar
       // hInstance: the first parameter from WinMain
       // NULL: not used in this application
       HWND hWnd = CreateWindowEx(
          WS_EX_OVERLAPPEDWINDOW,
          szWindowClass,
          szTitle,
          WS_OVERLAPPEDWINDOW,
          CW_USEDEFAULT, CW_USEDEFAULT,
          500, 100,
          NULL,
          NULL,
          hInstance,
          NULL
       );
    
       if (!hWnd)
       {
          MessageBox(NULL,
             _T("Call to CreateWindow failed!"),
             _T("Windows Desktop Guided Tour"),
             NULL);
    
          return 1;
       }
    
       // The parameters to ShowWindow explained:
       // hWnd: the value returned from CreateWindow
       // nCmdShow: the fourth parameter from WinMain
       ShowWindow(hWnd,
          nCmdShow);
       UpdateWindow(hWnd);
    
       // Main message loop:
       MSG msg;
       while (GetMessage(&msg, NULL, 0, 0))
       {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
       }
    
       return (int) msg.wParam;
    }
    
    //  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
    //
    //  PURPOSE:  Processes messages for the main window.
    //
    //  WM_PAINT    - Paint the main window
    //  WM_DESTROY  - post a quit message and return
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
       PAINTSTRUCT ps;
       HDC hdc;
       TCHAR greeting[] = _T("Hello, Windows desktop!");
    
       switch (message)
       {
       case WM_PAINT:
          hdc = BeginPaint(hWnd, &ps);
    
          // Here your application is laid out.
          // For this introduction, we just print out "Hello, Windows desktop!"
          // in the top left corner.
          TextOut(hdc,
             5, 5,
             greeting, _tcslen(greeting));
          // End application-specific layout section.
    
          EndPaint(hWnd, &ps);
          break;
       case WM_DESTROY:
          PostQuitMessage(0);
          break;
       default:
          return DefWindowProc(hWnd, message, wParam, lParam);
          break;
       }
    
       return 0;
    }
  2. On the Build menu, choose Build Solution. The results of the compilation should appear in the Output window in Visual Studio.

    Animation showing the steps to build the DesktopApp Project.

  3. To run the application, press F5. A window that contains the text «Hello, Windows desktop!» should appear in the upper-left corner of the display.

    Screenshot of the running DesktopApp Project.

Congratulations! You’ve completed this walkthrough and built a traditional Windows desktop application.

See also

Windows Desktop Applications

Время прочтения
8 мин

Просмотры 19K

Суровая действительность разработки на C и C++ для Windows такова: для этой платформы никогда не существовало качественной, нативной реализации стандартной библиотеки этих языков. Стандартная библиотека должна абстрагировать механизмы базовой системы ради упрощения разработки переносимого программного обеспечения. С и C++ на Windows очень плохо состыкованы с интерфейсами операционной системы. В результате большая часть переносимых, или, так сказать, «почти всегда переносимых» программ, которые отлично работают практически везде, в Windows оказываются едва заметно «поломанными», в особенности — за пределами англоговорящего мира. Причины этого почти наверняка связаны с политикой тех или иных компаний, с искусственными ограничениями, а не с техническими особенностями систем, что лишь усугубляет положение. Эта статья посвящена рассказу о проблемах Windows-разработки на C и C++ и о том, как они выражаются. Здесь же будут представлены некоторые простые методы борьбы с этими проблемами при разработке переносимого ПО.

Существует множество реализаций компилятора С. Как такое может быть, что все они не в порядке, даже те, что были созданы одними из первых? Библиотека времени выполнения Microsoft C определила то, как стандартная библиотека C должна работать в Windows, а все остальные реализации ради совместимости следовали за ней. Исключением я считаю платформу Cygwin и её главный форк — MSYS2, несмотря на то, что они не унаследовали описываемых недостатков. Они, в ходе эволюции, так сильно изменились, что, по сути, представляют собой совершенно новые платформы, которые нельзя назвать полностью соответствующими «обычной» Windows.

На практике стандартные библиотеки C++ реализованы на базе стандартной библиотеки C. Именно поэтому у C++ имеются те же проблемы, что и у C. CPython избегает этих проблем. Хотя эта реализация Python и написана на C, на Windows она обходит неправильную стандартную библиотеку C и напрямую обращается к проприетарным интерфейсам. Реализации других языков программирования, вроде gc в случае с Go, попросту создаются не на базе механизмов C, вместо этого, с самого начала, делая всё как нужно, поступая так, как библиотекам времени выполнения C стоило бы поступать уже давно.

Если вы работаете над единственным крупным проектом, обход библиотек времени выполнения C — это не так уж и сложно. И вы, вероятно, уже так и поступаете, обращаясь к важному функционалу платформы. Вам, по правде, даже и не нужна библиотека времени выполнения C. Но если вы заняты разработкой множества небольших программ (как я), то написание особого кода для их поддержки в Windows быстро станет основной частью вашей работы. И, откровенно говоря, обеспечение поддержки Windows не стоит подобных усилий. Я пришёл к тому, что чаще всего просто принимаю то, что мне предлагают по умолчанию, хотя и знаю, что это приведёт к проблемам в Windows.

Прежде чем мы перейдём к деталям, хочу кое-что предложить тем, кто хочет легко и быстро решить вышеозначенные проблемы при работе с набором инструментов Mingw-w64, включая w64devkit. Это — моя библиотека libwinsane. Благодаря ей ваши консольные программы, написанные на C и C++, будут работать правильно. Она решает все проблемы, о которых идёт речь в этой статье, за исключением одной. При этом для применения этой библиотеки менять ваш исходный код не нужно. Достаточно просто связать её с вашей программой.

Что конкретно работает неправильно?

Существует две разновидности Windows API: «узкий», с суффиксом A (ANSI), и «широкий» (Unicode, UTF-16) с суффиксом W. Первый — это устаревший API, где активная кодовая страница отображает 256 байт на 256 конкретных символов (поддерживается до 256 символов). На типичных компьютерах, настроенных на работу с европейскими языками, это означает применение кодовой страницы Windows-1252. Грубо говоря, внутри Windows используется кодировка UTF-16, а вызовы, выполняемые через «узкий» интерфейс используют активную кодовую страницу для перевода «узких» строк в «широкие». В результате у обращений к «узкому» API есть лишь ограниченный доступ к системе.

Кодировка UTF-8 изобретена в 1992 году, она была стандартизирована в январе 1993 года. В последующие годы мир Unix принял эту кодировку из-за её обратной совместимости с существующими интерфейсами. Программы могли читать и записывать Unicode-данные, могли пользоваться Unicode-путями, обрабатывать Unicode-аргументы, считывать значения Unicode-переменных окружения и устанавливать их значения. При этом в программах ничего не нужно было менять. В наши дни кодировка UTF-8 стала самым распространённым форматом кодирования текстовой информации. Во многом это так благодаря развитию Всемирной паутины.

В июле 1993 года Microsoft, с выходом Windows NT 3.1, представила «широкий» API Windows, сделав ставку на кодировку UCS-2 (позже — UTF-16), а не на UTF-8. Это, как оказалось, было ошибкой, так как UTF-16 практически во всём уступает UTF-8. Правда, надо признать, что тогда некоторые проблемы не были особенно очевидными.

Главная проблема заключается в том, что стандартные библиотеки C и C++ подключены лишь к «узкому» интерфейсу Windows. Стандартная библиотека, а значит — и типичное переносимое приложение на Windows, не может обрабатывать ничего кроме ASCII-кода. Это приводит к тому, что эти программы не могут выполнять следующие действия в том случае, если они предусматривают применение символов, отличных от ASCII-символов:

  • Принимать аргументы.
  • Читать и устанавливать значения переменных окружения.
  • Работать с путями.
  • Считывать и выводить данные в консоли.

Выполнение любого из этих действий требует вызова проприетарных функций. При этом Windows рассматривается в роли особой целевой платформы. Это часть того, что делает портирование программ на Windows весьма неприятным занятием. Разумное решение этой проблемы могло бы выглядеть как организация поддержки UTF-8 библиотекой времени выполнения C и подключение её к «широкому» API. Ещё один вариант решения проблемы заключается в том, что «узкий» API можно было бы перевести на UTF-8, постепенно отказываясь от концепции применения старой кодовой страницы. Это, в теории, то, что представляет собой «кодовая страница» UTF-8, хотя подобные решения оказываются работоспособными не всегда. При резком переходе на UTF-8 возникли бы проблемы с совместимостью. Но до совсем недавнего времени такая возможность не была представлена даже неким, условно говоря, «переключателем». Почему в Windows не может быть такого «переключателя» включив который можно обеспечить программам возможность нормально работать? Работать так же хорошо, как и на других платформах.

Как решить почти все проблемы поддержки Unicode?

В 2019 году Microsoft представила возможность, позволяющую программам при запуске запрашивать UTF-8 в роли их активной кодовой страницы. Большее, чем раньше, количество функций «узкого» API получило поддержку UTF-8. Это похоже на тот «переключатель», о котором я мечтал, но мою радость несколько омрачает то, что для реализации этих возможностей надо особенным образом встраивать в бинарники некоторый объём неприглядного XML-кода. Но теперь у нас, по крайней мере, появилась некая стандартная возможность работать с UTF-8.

При использовании Mingw-64 это означает необходимость создания такого файла ресурсов:

#include <winuser.h>
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "utf8.xml"

Далее, компилируем это с помощью windres:

$ windres -o manifest.o manifest.rc

То, что получилось, связываем с программой. Это, удивительным образом, обычно работает! Программы могут получать доступ к Unicode-аргументам, могут работать с переменными окружения, с путями (в том числе — с помощью fopen). В общем, всё работает так же, как, уже десятилетия, работает на других платформах. Так как активная кодовая страница устанавливается в момент загрузки программы, это событие происходит до конструирования argv (из GetCommandLineA), и именно поэтому всё это и работоспособно.

В качестве альтернативы можно создать так называемую «параллельную сборку» (SxS, side-by-side assembly), поместив этот XML-код в файл с тем же именем, что и у EXE-файла, но с расширением .manifest (после расширения .exe), а после этого положив этот файл около EXE-файла. Пользуясь этим приёмом, стоит помнить о существовании SxS-кеша (WinSxS). Изменения в соответствующих файлах могут быть видны лишь через некоторое время после их выполнения.

При использовании описываемого метода, правда, не работает консольный ввод/вывод. Консоль является, по отношению к процессу, внешней сущностью, поэтому на неё требования процесса к активной кодовой странице не распространяются. Её нужно особо настраивать, пользуясь проприетарным вызовом:

SetConsoleOutputCP(CP_UTF8);

Это, конечно, так себе занятие, но, по крайней мере, теперь всё не так плохо, как раньше. Правда, эта настройка имеет отношение лишь к выводу данных. То есть — программы могут писать в консоль, пользуясь UTF-8, но не читать из консоли. К сожалению, возможность чтения UTF-8-текстов из консоли всё ещё не работает. При установке кодовой страницы для входных данных нам сообщают об успешном проведении операции, но этим всё и ограничивается.

SetConsoleCP(CP_UTF8);  // не работает

Если вам нужно читать Unicode-данные из консоли в интерактивном режиме — вам не остаётся ничего кроме обхода библиотеки времени выполнения C, так как вышеописанный механизм всё ещё неработоспособен.

Преобразование текстовых потоков

Ещё одна давняя проблема программирования для Windows на C и C++ заключается в наличии отличающихся друг от друга «текстовых» и «двоичных» потоков, унаследованных от DOS. В основном это означает автоматическое преобразование символов перевода строки (CRLF и LF). Стандарт C это недвусмысленно позволяет, но на Unix-подобных платформах никогда не делалось различия между текстовыми и двоичными потоками.

Стандарт, кроме того, указывает на то, что стандартные потоки ввода, вывода и ошибок открываются в виде текстовых потоков, при этом нет переносимого способа переключить поток в двоичный режим. Это серьёзный недостаток стандарта. В Unix-подобных системах это неважно, но в Windows это означает, что программа не может читать или писать двоичные данные при работе со стандартными потоками, не вызывая нестандартные функции. Это означает ещё и то, что чтение стандартных потоков и запись в них выполняются медленно. Часто это становится узким местом программ, если только создатель программы не обойдёт этот недостаток.

Лично я предпочитаю писать двоичные данные в стандартный поток вывода, в том числе — видеоданные, и иногда пользуюсь двоичными фильтрами, которые тоже читают двоичные входные данные. Я делаю это так часто, что, вероятно, в половине моих C-программ, в функции main, имеется такой фрагмент кода, обеспечивающий их правильную работу в Windows:

    #ifdef _WIN32
    int _setmode(int, int);
    _setmode(0, 0x8000);
    _setmode(1, 0x8000);
    #endif

Эта магическая формула устанавливает стандартные потоки ввода и вывода в библиотеке времени выполнения C в двоичный режим. При этом не нужны никакие заголовочные файлы, код получается компактным, простым и самодостаточным.

Вышеописанное встроенное преобразование символов перевода строки, а также стандартный текстовый редактор Windows, Notepad, сильно отстающий от других систем, стали причиной того, что многие другие программы, включая Git, создали собственные, вызывающие неудобства, «полезные» возможности по преобразованию символов перевода строки, которые привели к другим проблемам.

Библиотека libwinsane

В начале статьи я говорил о моей библиотеке libwinsane. Она позволяет исправить все вышеописанные проблемы путём простого связывания её с программой. Сюда входит использование раздела .rsrc XML-манифеста, настройка консоли на вывод UTF-8-текстов, перевод стандартных потоков в двоичный режим. Всё это выполняется до вызова функции main (с помощью конструктора GCC). Я называю мою разработку «библиотекой», но это, на самом деле, всего лишь объектный файл. Эта разработка не может быть представлена в виде статической библиотеки, так как она должна быть связана с программой, несмотря на то, что в коде программы она нигде не упоминается.

Вот программа:

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    char *arg = argv[argc-1];
    size_t len = strlen(arg);
    printf("%zu %sn", len, arg);
}

В обычных условиях её компилируют и запускают так:

C:>cc -o example example.c
C:>example π
1 p

Как всегда, Unicode-аргументы по-тихому ужимаются до одного байта. А теперь свяжем эту программу с libwinsane:

C:>gcc -o example example.c libwinsane.o
C:>example π
2 π

Это приведёт к тому, что в Windows программа заработает так же, как на любой другой платформе.

Если вы занимаетесь поддержкой достаточно крупной программы, то вы, возможно, решите внедрить в свой проект необходимые вам части libwinsane, вместо того, чтобы постоянно связывать его с представленным объектным файлом, который даже не используется в самой программе. Причины его существования заключаются в основном в удобстве использования и в том, чтобы сжато продемонстрировать мои идеи. А в своей версии моего кода вы можете даже решить организовать обработку управляющих последовательностей ANSI.

С какими проблемами вы сталкивались, программируя для Windows на C и C++?

Теги: Первая программа на си. Азы си. Си Borland. Си Code Gear. Си Embarcadero. Си MS Visual Studio. Си MS Express.

Пишем первую программу на си

Для начала, необходимо установить программное обеспечение. В принципе не важно, каким ПО вы будете пользоваться, также как не важна и операционная система. Но в течение всего курса я буду приводить примеры на MS Visula Studio 2012 Express Edition. Visual Studio 2012 Express Edition бесплатный и его за глаза хватит для изучения всего курса. Кроме того, как показала практика, он гораздо строже относится к коду и даёт более полноценное описание ошибок и предупреждений.
При изучении языка можно использовать Borland (он же CodeGEAR, он же Embarcadero и т.д.), Dev Cpp, MinGW, или gcc, или что вы ещё захотите.

Пример для MS Visual Studio

1. Открываем IDE, заходим Файл | Создать проект...

Создаём проект.

Создаём проект.

2. Выбираем консольное приложение и даём ему имя. В данном случае first_program

Выбираем 'консольное приложение Win32' и даём ему имя.

Выбираем ‘консольное приложение Win32’ и даём ему имя.

3. Далее…

Далее...

Далее…

4. Ставим галочку «Пустой проект».

Создаём пустой проект.

Создаём пустой проект.

5. После чего получаем пустую структуру проекта. Добавим новый элемент: правый клик мыши по папке
"Файлы исходного кода" | Добавить | Создать элемент...

Добавляем файл в проект.

Добавляем файл в проект.

Добавляем новый cpp файл, но сохраняем его с расширением .c

Создаём .c файл.

Создаём .c файл.

Я назвал файл main.c
Всё, готово, можно писать программу. Пропустите шаги для других платформ.

Borland

У меня установлен только Code Gear C++Builder 2007, но в остальных (и предыдущих) релизах всё делается также.

1. Создадим новый проект File | New | Other...

Создаём новый проект.

Создаём новый проект.

2. Добавляем консольное приложение

Добавляем консольное приложение.

Добавляем консольное приложение.

3. Выбираем язык си

Выбираем язык си.

Выбираем язык си.

4. Получаем готовый проект. Его необходимо сохранить с тем именем, которое захотите. До тех пор сам проект и все файлы будут иметь имена по умолчанию. Вы можете удалить то, что Borland по умолчанию прописал в тексте программы.

Готовый проект на борланде. Его необходимо сохранить.

Готовый проект на борланде. Его необходимо сохранить.

Пример для cc/gcc для терминала

Откройте ваш любимый текстовый редактор и скопируйте туда код программы.

#include <stdio.h>

int main(int argc, char* argv[]) {
	printf("Hello, World!");
	scanf("1");
	return 0;
}

Если вы сохранили программу в файле с именем hello.c, то наберите в терминале команду


cc hello.c -o hello


либо


gcc hello -o hello

При этом, очевидно, вы должны находиться в папке с программой. gcc создаст исполняемый файл с именем hello. Запустите его, и он выведет Hello, World!


./hello


Иногда могут возникнуть проблемы с правами доступа. Проверьте, что у вас исполняемый файл, иначе дайте себе привелегии на запуск.


chmod 760 hello

Если у вас несколько файлов, то необходимо будет перечислить имена всех си файлов по порядку. Например, если у вас есть ещё два файла simple.h и simple.c, то нужно прописать


cc hello.c simple.c -o hello

Код программы

Принято в первой программе выводить Hello, World! на экран.

#include <stdio.h>
#include <conio.h>

int main(int argc, char* argv[]) {
	printf("Hello, World!");
	_getch();
	return 0;
}

Запустите программу ( Run | Run или F9 для борланда, Построение | Построить решение или F5 для MS)
Программа выведет Hello, World! и будет ждать, когда вы нажмёте на любую клавишу.

Рассмотрим код подробнее.
Первые две строки

#include <stdio.h>
#include <conio.h>

директивы компилятору на подключение стандартных библиотек stdio (Standard Input Output — стандартная библиотека ввода вывода) и conio (Console Input Output — стандартная библиотека консоли вывода вывода).
Расширение .h указывает, что это заголовочные файлы (header files).

Компилятор копирует код библиотек conio и stdio, и даёт возможность использовать функции, описанные в этих библиотеках.

int main(int argc, char* argv[]) 

Это функция main. Она отличается от остальных функций, которые вы можете определить тем, что является точкой входа — с неё начинается выполнение программы.

Функция main имеет два параметра — число параметров argc и массив переданных параметров argv. Эти аргументы необязательные, поэтому можно их не писать. Об их использовании поговорим позже.

#include <stdio.h>
#include <conio.h>

int main() {
	printf("Hello, World!");
	_getch();
	return 0;
}

Функция main должна возвращать целое число. Если это 0, то функция отработала без ошибок.
В современном стандарте си можно не возвращать 0, и описать функцию как void main.

#include <stdio.h>
#include <conio.h>

void main() {
	printf("Hello, World!");
	_getch();
}

Наша программа теперь выглядит совсем просто.

Строка

printf("Hello, World!");

выводит строку Hello, World! на экран монитора.

_getch()

ожидает нажатия на клавишу.

Давайте сделаем что-нибудь посложнее, чтобы научиться добавлять новые файлы в программу. Сейчас для вас важно научиться добавлять новые файлы, если часть кода останется непонятной, это не беда.

1. Создайте новый заголовочный файл в папке «Заголовочные файлы», назовите его simple.h
2. Создайте новый файл simple.c в папке «Файлы исходного кода».
3. Добавьте в simple.h

#ifndef _SIMPLE_H_
#define _SIMPLE_H_

#include <stdio.h>
#include <conio.h>

void doSomething();

#endif

Здесь мы объявили новую функцию doSomething. У неё отсутствует тело, оно будет описано в файле simple.c. Здесь же мы подключаем и библиотеки stdio и conio
Добавьте в simple .c

#include "simple.h"

void doSomething() {
	printf("It works!");
	_getch();
}

Мы включаем в файл simple.c заголовочный файл. Он пишется в двойных кавычках, потому что это не файл из стандартной библиотеки. Файлы стандартной библиотеки обычно располагаются в папке include самой IDE. Если поместить туда наши файлы, то их тоже можно будет объявлять в угловых скобках. В двойных кавычках можно также прописывать абсолютные пути к файлам.
Так как мы уже включили библиотеки conio и stdio в .h файле, то они «видны» и в .c файле.
Далее, в main.c

#include "simple.h"

int main(int argc, char* argv[]) {
	doSomething();
	return 0;
}

Мы подключаем только заголовочный файл. Содержимое simple.c будет добавлено автоматически. Собираем проект (F5 или F9, или что там у вас за среда…) Если у вас всё заработало то отлично, вы научились добавлять новые файлы в проект.

Q&A

Всё ещё не понятно? – пиши вопросы на ящик email

История Языка

https://gbcdn.mrgcdn.ru/uploads/post/2321/og_image/a9e54f9e3bfc6606821429d50bd15597.png

IDE

Что же нам нужно для создания первого приложения? Во-первых, нужен текстовый редактор, в котором мы будем писать исходный код. Во-вторых, нам понадобится компилятор, который преобразует исходный код в исполняемый файл (например, .exe-файл в операционной системе Windows). В-третьих, нужен фреймворк .NET, который необходим для компиляции и запуска приложения — о фреймворке .NET мы говорили в предыдущей статье.

Все элементы, необходимые для создания первого приложения, объединены в специальной программе IDE (англ. Integrated Development Environment), а именно:

  • Кодовый редактор
  • Транслятор (компилятор и/или интерпретатор)
  • Средства автоматизированной сборки
  • Отладчик

Также IDE может включать в себя средства для интеграции с системами управления версиями и разнообразные инструменты для упрощения конструирования графического интерфейса пользователя. И это ещё не всё. Как правило, каждая IDE предназначена для нескольких языков программирования. Для разработки на языке C# можно выделить следующие среды разработки:

  • Visual Studio. Разработана компанией Microsoft (так же, как и язык C#), поэтому является основной для создания приложений на C#.
  • Project Rider. Это кроссплатформенная интегрированная среда разработки программного обеспечения для платформы .NET, разрабатываемая компанией JetBrains.
  • Sharp Developer. Свободная среда разработки для языка C#, альтернатива Visual Studio .NET.
  • Eclipse. Данная IDE предназначена в основном для разработки Java-приложений, но и на C# также можно создавать приложения.
  • Visual Studio Code. Редактор исходного кода, разработанный Microsoft для Windows, Linux и macOS. Позиционируется как «лёгкий» редактор кода для кроссплатформенной разработки веб- и облачных приложений.

Создавать первое приложение на C# мы будем в интегрированной среде разработки Visual Studio. На официальном сайте доступны три версии продукта:

  • Community – полнофункциональная, расширяемая и бесплатная версия интегрированной среды разработки для создания современных приложений Android, iOS и Windows, а также веб-приложений и облачных служб.
  • Professional – платная версия, содержащая профессиональные инструменты и службы для индивидуальных разработчиков или небольших команд.
  • Enterprise – платная версия, представляющая интегрированное комплексное решение для команд любого размера с высокими требованиями к качеству и масштабу.

Для создания приложений на C# будем использовать бесплатную и полнофункциональную среду разработки — Visual Studio Community 2019.

Установка Visual Studio

После загрузки необходимо запустить установщик. В открывшемся окне нам предложат выбрать те компоненты, которые мы хотим установить вместе с Visual Studio. Стоит отметить, что Visual Studio — очень функциональная среда разработки и позволяет разрабатывать приложения с помощью множества языков и платформ.

В нашем случае интересен прежде всего C# и .NET Core, поэтому в наборе рабочих нагрузок можно выбрать только пункт «Кроссплатформенная разработка .NET Core». Можно выбрать и больше опций или вообще все опции, однако стоит учитывать свободный размер на жёстком диске — чем больше опций будет выбрано, тем больше места на диске будет занято.

При инсталляции Visual Studio на ваш компьютер будут установлены все необходимые инструменты для разработки программ, в том числе фреймворк .NET Core. Установка успешна? Начинаем писать программу!

Создание проекта в Visual Studio

Откройте Visual Studio и на стартовом экране выберите пункт «Создание проекта».

На следующем окне в качестве типа проекта нужно выбрать Консольное приложение (.NET Core). Это значит, что мы будем создавать приложение командной строки на языке C#.

В следующем окне зададим название проекта. Пусть будет HelloWorld. На этом этапе также можно указать папку, где будет располагаться проект. После этого нажмите кнопку «Создать».

Visual Studio создаст и откроет проект. Окно будет выглядеть так:

В нашем редакторе в центре находится сгенерированный по умолчанию код C#. Впоследствии мы изменим его на свой. Слева находится обозреватель решений, в котором можно увидеть структуру нашего проекта. В данном случае в обозревателе сгенерирована структура по умолчанию. В узле «Зависимости» содержатся сборки, которые добавлены в проект по умолчанию — классы библиотеки .NET, которые будет использовать C#. Однако не всегда все сборки нужны. Лишнее содержимое отсюда потом можно удалить. Или, наоборот, добавить какую-нибудь нужную библиотеку — именно в этом узле она будет размещена.

Hello world

Под узлом «Зависимости» расположен непосредственно сам файл кода программы — Program.cs. Как раз он и открыт в центральном окне. Вначале разберёмся, что весь этот код собой представляет:

using System;
 
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

В начале файла мы видим директиву using, после которой идёт название подключаемого пространства имён. Пространства имён необходимы для организации классов в общие блоки. Например, в первой строке подключается пространство имён System, которое содержит фундаментальные и базовые классы платформы .NET.

C# имеет C-подобный синтаксис, и каждая строка завершается точкой с запятой, а каждый блок кода помещается в фигурные скобки. Далее начинается уже наше пространство имён HelloWorld, которое будет создавать отдельную сборку или исполняемую программу. Сначала идёт ключевое слово namespace, после которого следует название пространства имён. По умолчанию Visual Studio даёт ему название проекта. Далее внутри фигурных скобок идёт блок пространства имён.

Пространство имён может включать другие пространства или классы. В нашем случае по умолчанию сгенерирован один класс — Program. Классы объявляются похожим способом: сначала идёт ключевое слово class, а потом название класса, и далее блок самого класса в фигурных скобках.

Класс может содержать различные переменные, методы, свойства, прочие инструкции. В данном случае объявлен один метод Main. В программе на C# метод Main является входной точкой программы, с него начинается всё управление. Это обязательный элемент любой программы.

Слово static указывает, что метод Main статический, а слово void — что он не возвращает никакого значения. Далее в скобках у нас идут параметры метода. string[] args — это массив с именем args, который хранит значения типа string, то есть строки. В данном случае они нам пока не нужны, но в реальной программе это те параметры, которые передаются при запуске программы из консоли.

Внутри метода располагаются действия, которые этот метод выполняет. По умолчанию он содержит одно действие: Console.WriteLine(«Hello World!»); — выводит в консоль строку «Hello World!».

Теперь мы можем запустить программу на выполнение с помощью клавиши F5 или с панели инструментов, нажав на зелёную стрелку. И если вы все сделали правильно, то при запуске приложения увидите заветную строку.

Интерактивное приложение на C#

Теперь сделаем всё поинтересней — изменим код на следующий:

using System;
 
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Введите свое имя: ");
            string name = Console.ReadLine();
            Console.WriteLine($"Привет {name}");   
        }
    }
}

По сравнению с автоматически сгенерированным кодом были внесены несколько изменений. Теперь в методе Main первой строкой выводится приглашение к вводу.

Класс Console, метод которого мы вызываем, находится в пространстве имён System. Это пространство подключено в начале с помощью директивы using. Без подключения пространства имён System невозможно было бы использовать класс Console.

Однако нам необязательно подключать пространство имён. Мы можем даже удалить первую строку, но в этом случае мы тогда должны будем указать полный путь до используемого класса:

System.Console.Write("Введите свое имя: ");

Во второй строке определяется строковая переменная name (тип string), в которую пользователь вводит информацию с консоли:

string name = Console.ReadLine();

Мы обозначили, что помощью метода ReadLine() можем считать с консоли строку.

Затем введённое имя выводится на консоль:

Console.WriteLine($"Привет {name}"); 

Чтобы задать вывод значения переменной name в выводимой на консоль строке, применяются фигурные скобки {}. При выводе строки на консоль выражение {name} будет заменяться на значение переменной name — введённое имя.

Знак доллара ($) обозначает, что внутри строки таким образом можно выводить значения переменных. Теперь протестируем проект, запустив его выполнение.

Поздравляю, мы создали первое приложение! Исполняемый файл вы можете найти на жёстком диске в папке проекта в каталоге bin/Debug. Оно будет называться так же, как проект, и иметь расширение .exe. Этот файл можно запускать без Visual Studio, а также переносить для доработки на другие компьютеры, где есть .NET Core.

На этом я заканчиваю обсуждение теории, лежащей в основе программирования для Windows. Пришло время написать вашу первую программу для Windows. Во всех, рассматриваемых в этой книге примерах, я использую компилятор Microsoft Visual C++ 6.0. Если у вас еще нет этой программы, я настоятельно рекомендую пойти и приобрести ее.

СОВЕТ

Когда я проверял последний раз, версия Visual C++ 6.0 Standard стоила около 100 долларов США. Вероятно, вы сможете приобрести ее еще дешевле, присоединившись к программе скидок для студентов. Это хорошее вложение денег.

Начало работы с Visual C++ 6.0

Давайте продолжим. Запустите вашу копию Visual C++ 6.0. Вы должны увидеть экран, аналогичный представленному на рис. 2.2.

Рис. 2.2. Интерфейс Visual C++ 6.0

Рис. 2.2. Интерфейс Visual C++ 6.0

В интерфейсе, представленном на рис. 2.2, нет ничего особенного. Основные элементы управления сосредоточены в верхнем меню: File, Edit, View, Insert и т.д. Все, что вы делаете в Visual C++, должно быть связано с проектом. Проект — это верхний уровень иерархии в среде разработки.

Как создать проект

Лучший способ обучения — практика, так что давайте создадим новый проект, для чего щелкнем по меню File и выберем пункт New. Вам будет предоставлено множество вариантов, изображенных на рис. 2.3.

Рис. 2.3. Диалоговое окно создания нового проекта в Visual C++

Рис. 2.3. Диалоговое окно создания нового проекта в Visual C++

Диалоговое окно, которое вы видите, используется для указания типа создаваемого проекта. Как видно Visual C++ используется для создания всех типов программ, включая элементы управления ActiveX, COM-объекты, надстройки DevStudio и конечно же приложения Windows. Нас интересует тип приложений, который называется Win32 Application. Этот тип используется при создании программ для Windows. Выделите в списке пункт Win32 Application чтобы выбрать этот тип.

Перед тем, как двинуться дальше, вы должны сообщить Visual C++ имя вашего проекта и где он будет храниться. Эта информация вводится в текстовые поля Project name и Location. Для нашего примера в качестве имени проекта укажите CreateWindow.

СОВЕТ

Я храню весь исходный код в папке C:Source. Рекомендую вам создать аналогичную структуру каталогов, где будут храниться исходные тексты ваших программ. Лучше хранить весь код в единственном месте, чем разбрасывать его по всему жесткому диску.

После того, как вы введете требуемую информацию, щелкните по кнопке OK. Вам будет предложено диалоговое окно, показанное на рис. 2.4.

Рис. 2.4. Диалоговое окно выбора типа Windows-приложения

Рис. 2.4. Диалоговое окно выбора типа Windows-приложения

Диалоговое окно, представленное на рис. 2.4, предлагает три варианта:

  • An empty project — пустой проект;
  • A simple Win32 application — простое приложение Win32;
  • A typical «Hello World!» application — типичное приложение «Hello World».

Первый вариант, пустой проект, используется наиболее часто. Его выбор приводит к созданию пустого проекта в который не включены никакие объекты. Фактически создается чистый холст для последующей работы.

Второй вариант, простое приложение Win32, используется реже, поскольку все, что он делает — это создает программу, которая ничего не делает. В этом случае для вас создаются исходные файлы, но они делают не слишком много.

Третий вариант, типичное приложение «Hello World!», делает больше, чем два предыдущих. Он создает законченую программу для Windows, которая выводит текст «Hello World!». Я не использую этот вариант слишком часто, поскольку создаваемый код на мой вкус выглядит слишком загроможденным.

Выберите первый переключатель и щелкните по кнопке Finish, чтобы открыть диалоговое окно, изображенное на рис. 2.5.

Рис. 2.5. Диалоговое окно подтверждения выбранных параметров

Рис. 2.5. Диалоговое окно подтверждения выбранных параметров

Представленное диалоговое окно не содержит ничего необычного. Оно выводится только для того, чтобы вы могли удостовериться, что будет создан проект желаемого типа. Для продолжения работы щелкните по кнопке OK.

Рабочее пространство

Итак, вот ваш проект во всей славе! Теперь, после того как проект создан, настало время подробнее поговорить о нем. Взгляните на рис. 2.6.

Рис. 2.6. Пустое пространство проекта для вашей новой программы

Рис. 2.6. Пустое пространство проекта для вашей новой программы

Взгляните на рабочее пространство — область в левой части, содержащую имя вашего проекта со знаком «+» рядом с ним. Перейдите на вкладку FileView и разверните дерево, щелкнув по знаку «+». Вы увидите три подпапки в дереве файлов проекта:

  • Source Files — исходные файлы;
  • Header Files — заголовочные файлы;
  • Resource Files — файлы ресурсов.

Надеюсь, вы знаете как программировать на С или С++ и для чего предназначены исходные и заголовочные файлы. Если нет, я рекомендую вам приобрести книгу по программированию на С++ и изучить ее перед тем, как продолжать чтение. Без этих знаний вам придется очень трудно.

ПРИМЕЧАНИЕ

Рабочее пространство — это второй уровень иерархии в среде разработки. В начале цепочки расположен проект, затем следует рабочее пространство. Файлы вашего проекта расположены в конце цепочки. В одном проекте может быть несколько рабочих пространств, точно также как в одном рабочем пространстве может быть несколько исходных файлов.

Файлы ресурсов

Если вы умеете программировать, но являетесь новичком в программировании для Windows, вам может быть непонятно назначение папки Resource Files.

В программировании для Windows ресурсами называются элементы, используемые для расширения или добавляемые к приложению. Примерами ресурсов могут служить значки, растровые изображения, звуковые файлы и текстовые строки. Прелесть ресурсов в том, что они компилируются в вашу программу. Вам не надо беспокоиться об их установке на компьютер пользователя, ведь они являются частью исполняемого файла.

СОВЕТ

Ресурсы удобны, но не стоит включать в программу слишком много их. Если поступать так, полученные исполняемые файлы будут слишком большими. Это вызовет проблемы, когда для пользователей будет выпускаться обновление исполняемых файлов. Вместо того, чтобы загрузить компактный исполняемый файл, им придется загружать огромный файл, полный ресурсов.

Я не хочу потеряться среди связанных с ресурсами тем, так что давайте отправимся дальше. В вашем проекте пока нет файлов, поэтому в папках ничего не перечислено. Давайте исправим это, добавив в проект файл с исходным кодом на С++.

Добавление исходных файлов в проект

Раскройте меню Project и выберите пункт Project | Add To Project | New. В результате на экран будет выведено диалоговое окно, изображенное на рис. 2.7.

Рис. 2.7. Диалоговое окно для выбора типа добавляемого к проекту файла

Рис. 2.7. Диалоговое окно для выбора типа добавляемого к проекту файла

Фактически это то же самое диалоговое окно, что и изображенное на рис. 2.3, просто в нем выбрана другая вкладка. Вместо того, чтобы выбирать тип создаваемого проекта, сейчас мы будем выбирать тип добавляемого файла. Больше всего нас должны интересовать типы C/C++ Header File и C++ Source File. Перед тем, как продолжить, выберите тип C++ Source File.

Мы уже на финишной прямой, но еще не закончили. Перед тем, как щелкнуть по кнопке OK, вы должны задать имя создаваемого файла. Введите в качестве имени файла CreateWindow и щелкните по кнопке OK.

ПРИМЕЧАНИЕ

Вы не обязаны давать исходному файлу и проекту одинаковые имена. Я делаю это только ради организации информации. Не стесняйтесь называть файл с исходным кодом, как вы желаете. (Некоторые люди используют имя Main.)

СОВЕТ

Когда вы создаете новый файл для проекта к нему добавляется требуемое расширение. Вам не надо вводить его. Например, если вы создадите файл с исходным кодом C++ и укажете имя Main, на жестком диске он будет сохранен как файл Main.cpp.

Теперь, когда файл создан и добавлен к проекту, вы можете видеть его в окне рабочего пространства. Взгляните на рис. 2.8.

Рис. 2.8. Рабочее пространство с добавленным файлом

Рис. 2.8. Рабочее пространство с добавленным файлом

Я раскрыл папку Source Files, чтобы показать только что созданный файл CreateWindow.cpp. Когда вы дважды щелкаете по любому файлу, представленному в окне рабочего пространства, он загружается и выводится в области редактирования. На рис. 2.8 загружен файл CreateWindow.cpp. В данный момент он пуст и ждет, что вы добавите в него какой-нибудь текст.

Погружаемся и сталкиваемся с кодом

Теперь, когда у вас есть чистый исходный файл, призывающий вас, настало время написать действительный код для Windows. Не волнуйтесь слишком много, я проведу вас через каждый шаг. Взгляните на приведенный ниже листинг. В нем полностью приведена рабочая программа для Windows.

// Стандартный включаемый файл Windows
#include <windows.h>

// Прототип функции обратного вызова для обработки сообщений
LRESULT CALLBACK fnMessageProcessor (HWND, UINT, WPARAM, LPARAM);

// Функция вызывается автоматически, когда программа запускается
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,
                    int iCmdShow)
{
   HWND         hWnd;
   MSG          msg;
   WNDCLASSEX   wndclass;

   // Настройка класса окна
   wndclass.cbSize        = sizeof(WNDCLASSEX);
   wndclass.style         = CS_HREDRAW | CS_VREDRAW;
   wndclass.lpfnWndProc   = fnMessageProcessor;
   wndclass.cbClsExtra    = 0;
   wndclass.cbWndExtra    = 0;
   wndclass.hInstance     = hInstance;
   wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
   wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
   wndclass.lpszMenuName  = NULL;
   wndclass.lpszClassName = "Window Class"; // Имя класса
   wndclass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

   // Регистрация класса окна
   if(RegisterClassEx(&wndclass) == 0)
   {
       // Сбой программы, выход
       exit(1);
   }

   // Создание окна
   hWnd = CreateWindowEx(
      WS_EX_OVERLAPPEDWINDOW,
      "Window Class",            // Имя класса
      "Create Window Example",   // Текст заголовка
      WS_OVERLAPPEDWINDOW,
      0,
      0,
      320,
      200,
      NULL,
      NULL,
      hInstance,
      NULL);

   // Отображение окна
   ShowWindow(hWnd, iCmdShow);

   // Обработка сообщений, пока программа не будет прервана
   while(GetMessage (&msg, NULL, 0, 0))
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

   return (msg.wParam);
}

// Функция обратного вызова для обработки сообщений 
// (НЕОБХОДИМА ВСЕМ ПРОГРАММАМ ДЛЯ WINDOWS)
LRESULT CALLBACK fnMessageProcessor (HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
   switch(iMsg)
   {
      // Вызывается, когда впервые создается окно
      case WM_CREATE:
         return(0);
      // Вызывается, когда окно обновляется
      case WM_PAINT:
         return(0);
      // Вызывается, когда пользователь закрывает окно
      case WM_DESTROY:
         PostQuitMessage(0);
         return(0);
      default:
         return DefWindowProc(hWnd, iMsg, wParam, lParam);
   }
}

Включение файлов и прототипы функций

В нескольких первых строках кода указываются включаемые заголовочные файлы и приводятся прототипы функций. В программировании для Windows необходим только один заголовочный файл с именем windows.h.

Для нашего примера требуется единственный прототип функции для обработчика сообщений. Любая программа для Windows должна содержать функцию, которая будет вызываться для обработки сообщений и должна соответствовать следующему прототипу:

LRESULT CALLBACK fnMessageProcessor (HWND, UINT, WPARAM, LPARAM);

Имя функции может отличаться, но параметры должны оставаться неприкосновенными. Это вызвано тем, что Windows автоматически вызываеет данную функцию и не сможет работать правильно, если вы измените параметры.

Внутреннее устройство функции WinMain()

Первый элемент, упоминаемый в функции WinMain() — это объект, используемый для создания окна. Объект является структурой типа WNDCLASSEX и очень важен для создания окна вашей программы.

Структура данных WNDCLASSEX

Если в прошлом вы уже занимались программированием для Windows, то могли использовать структуру WNDCLASS. Между ней и объектом WNDCLASSEX есть только пара отличий. Во-первых добавлен апраметр, задающий размер структуры в байтах. Во-вторых, добавлен параметр задающий дескриптор маленького значка для окна.

Ниже приведен прототип структуры WNDCLASSEX:

typedef struct _WNDCLASSEX {
   UINT    cbSize;
   UINT    style;
   WNDPROC lpfnWndProc;
   int     cbClsExtra;
   int     cbWndExtra;
   HANDLE  hInstance;
   HICON   hIcon;
   HCURSOR hCursor;
   HBRUSH  hbrBackground;
   LPCTSTR lpszMenuName;
   LPCTSTR lpszClassName;
   HICON   hIconSm;
} WNDCLASSEX;

Нравится ли вам встеча со сложной структурой, назначение членов которой надо запомнить? Нет! Хорошо хоть Microsoft позаботилась, чтобы имена этих переменных было просто читать. Первая переменная типа UINT называется cbSize. Она используется для указания размера структуры данных. Обычно для инициализации этого члена структуры используется выражение sizeof(WNDCLASSEX). Инициализацию этого члена данных вы можете увидеть взглянув на приведенный выше листинг.

ПРИМЕЧАНИЕ

Если вам непонятно ключевое слово

UINT

 — оно обозначает целое число без знака. Это означает, что переменная может принимать только положительные значения.

Второй элемент структуры также имеет тип UINT и называется style. Как указывает имя, член style используется для задания стиля создаваемого окна. Удобство данного члена данных в том, что вы можете указывать комбинации одних стилей с другими, используя несколько флагов, объединеных поразрядной операцией ИЛИ (|). Доступные стили перечислены в таблице 2.2.

Таблица 2.2. Стили окон

Значение Действие
CS_BYTEALIGNCLIENT Выравнивает клиентскую область окна (область с содержимым) по границе байта в направлении оси X. Оказывает влияние на позицию окна по горизонтали и на его ширину. Лично я никогда не использовал этот стиль.
CS_BYTEALIGNWINDOW Выравнивает окно по границе байта в направлении оси X. Оказывает влияние на границу окна по горизонтали и на его ширину. Этот стиль я также не использую.
CS_CLASSDC Размещает в структуре класса единый контекст устройства для использования всеми окнами. Я пока не рассказывал о контекстах устройств, но сделаю это позже, так что нет причин для волнения. В Windows можно создать несколько классов окон одного и того же типа в разных потоках. Так как у вас может быть несколько копий класса, несколько потоков могут попытаться использовать контекст устройства одновременно. Это вызовет блокировку всех потоков, кроме одного, пока этот поток не завершит работу с контекстом.
CS_DBLCLKS Очень простой стиль. Когда он указан, Windows будет посылать вашему окну сообщение о двойном щелчке каждый раз, когда пользователь выполняет двойной щелчок кнопкой мыши в пределах области окна. Это может показаться глупым, но многие приложения запоминают время каждого щелчка кнопки мыши, чтобы определить был ли выполнен двойной щелчок. Я, чтобы добиться того же, просто использую данный стиль.
CS_GLOBALCLASS Этот стиль разрешает создание глобального класса окна. Чтобы получить дополнительную информацию, обратитесь к документации, поставляемой с Microsoft Visual C++. При разработке игр нет необходимости использовать данный стиль.
CS_HREDRAW Этот стиль заставляет перерисовывать все окно в случае изменения его ширины.
CS_NOCLOSE Запрещает выполнение закрытия окна через системное меню.
CS_OWNDC Выделяет уникальный контекст устройства для каждого окна, созданного с использованием класса.
CS_PARENTDC Устанавливается, когда дочернее окно использует ту же самую область отсечения, что и родительское. Это позволяет дочернему окну рисовать на родительском. Это не означает, что потомок использует контекст устройства родителя. В действительности потомок получает свой собственный контекст устройства из системного пула. Единственное, что делает этот флаг — увеличение производительности приложения.
CS_SAVEBITS Сохраняет в памяти растровое изображение находящейся под окном области экрана. В дальнейшем при перемещении окна скрытая область копируется на экран. Это предотвращает отправку сообщения WM_PAINT всем окнам, которые были скрыты данным.
CS_VREDRAW Заставляет перерисовывать все содержимое окна в случае изменения высоты окна.

Третий член структуры имеет тип WNDPROC, является указателем на функцию и называется lpfnWndProc. Помещаемый сюда указатель должен указывать на функцию обработки сообщений Windows, которую окно использует чтобы принимать сообщения. Это очень важно, и функция, на которую ссылаются здесь должна полностью соответствовать прототипу, приведенному в моем коде. Взгляните на рис. 2.9, чтобы увидеть взаимоотношения между классом и обработчиком сообщений.

Рис. 2.9. Взаимоотношения между классом окна и обработчиком сообщений

Рис. 2.9. Взаимоотношения между классом окна и обработчиком сообщений

Четвертый член структуры относится к типу int и называется cbClsExtra. Это целое число задает количество байт, которые будут выделены сразу за структурой данных класса окна. Я не имею ни малейшего представления, для чего это может быть нужно, поскольку во всех примерах, которые я видел, это значение устанавливалось равным 0. Я рекомендую вам поступать точно так же. Если желаете, можете просто игнорировать этот член данных, поскольку система сама по умолчанию присваивает ему нулевое значение.

Пятый элемент также относится к типу int и называется cbWndExtra. Это целое число задает количество байтов, которые будут выделены сразу за экземпляром окна. Работа с ним аналогична обращению с предыдущим членом структуры, и система также по умолчанию присваивает ему нулевое значение.

Шестой член структуры имеет тип HANDLE и называется hInstance. Дескриптор, который вы задаете здесь, является дескриптором экземпляра к которому относится окнонная процедура класса. В большинстве случаев можно задать значение дескриптора hInstance, получаемого функцией WinMain() в одном из параметров.

Седьмой параметр называется hIcon и имеет тип HICON. Тип HICON это ни что иное, как замаскированный тип HANDLE, поэтому он не должен смущать вас. Данный дескриптор указывает на класс значка, используемого окном. Класс значка в действительности является ресурсом значка. Не присваивайте этой переменной значение NULL, поскольку если сделать так, то программа будет перерисовывать изображение значка, при каждом свертывании окна.

Если вы еще раз взглянете на код, то увидите, что для инициализации седьмого параметра я использую вызов функции LoadIcon().

Функция LoadIcon() загружает ресурс значка из исполняемой программы. Хотя ресурсы компилируются внутрь вашей программы для Windows, вам все равно необходимо загружать их, поэтому и требуется вызов данной функции. Вот как выглядит ее прототип:

HICON LoadIcon(
   HINSTANCE hInstance,
   LPCTSTR lpIconName
);

К счастью, у этой функции всего два прарметра — HINSTANCE и LPCTSTR. Первый параметр с именем hInstance, содержит дескриптор экземпляра модуля чей исполняемый файл содержит значок, который вы желаете использовать. В моих примерах программ я присваиваю этому параметру значение NULL. Делая так вы разрешите использование встроенных значов Windows, которые часто также называют стандартными значками.

Второй параметр является указателем на строку, содержащую имя загружаемого значка. Взглянув на разбираемый пример, вы увидите, что для инициализации данного параметра я использую константу IDI_APPLICATION. Ее значение соответствует значку, используемому по умолчанию для приложений, который вы могли видеть во многих программах для Windows. Остальные стандартные значения перечислены в таблице 2.3.

Таблица 2.3. Константы для стандартных значков

Значение Описание
IDI_APPLICATION Значок, используемый по умолчанию для приложений. Он применяется и в рассматриваемом примере. В большинстве случаев вы можете использовать это значение, если только не хотите, чтобы у вашего приложения был нестандартный значок.
IDI_ASTERISK Данное значение создает для вашей программы значок в виде небольшого овала с буквой «i» внутри.
IDI_ERROR Красный круг с крестом внутри.
IDI_EXCLAMATION Желтый треугольник с восклицательным знаком внутри.
IDI_HAND Я понятия не имею, для чего нужны эти дубли, но этот значок выглядит точно так же, как IDI_ERROR.
IDI_INFORMATION Еще один дубль. Это значение задает небольшой овальный значок, аналогичный задаваемому значением IDI_ASTERISK.
IDI_QUESTION Использование этого значения даст вашему приложению значок с вопросительным знаком.
IDI_WARNING О, опять дублирование. На этот раз значок выглядит так же как для значения IDI_EXCLAMATION.
IDI_WINLOGO Если использовать эту константу, значок вашего приложения будет выглядеть как небольшой аккуратный логотип Windows.

Вот и все, что можно сказать о функции LoadIcon(). Теперь вернемся к разбираемой программе. Ох, подождите, прежде чем продолжить, взгляните на рис. 2.10, где изображены некоторые стандартные значки.

Рис. 2.10. Некоторые стандартные значки Windows

Рис. 2.10. Некоторые стандартные значки Windows. ©2002 Microsoft, All Rights Reserved.

Восьмой член структуры данных WNDCLASSEX очень похож на седьмой, за исключением того, что он задает используемый окном курсор. Его тип HCURSOR, а имя — hCursor. Тип HCURSOR это еще один замаскированный дескриптор. Обычно здесь указывается значение дескриптора класса курсора, который будет использован в вашей программе для ее собственного курсора. Но вместо того, чтобы создавать нестандартный курсор, я воспользуюсь функцией LoadCursor().

Функция LoadCursor() похожа на функцию LoadIcon() за исключением того, что она загружает ресурсы курсора, а не ресурсы значка. Вот ее прототип:

HCURSOR LoadCursor(
   HINSTANCE hInstance,
   LPCTSTR lpCursorName
);

Первый параметр называется hInstance, и содержит дескриптор экземпляра модуля, чей исполняемый файл содержит курсор, который вы собираетесь использовать. В своем примере я присваиваю данному параметру значение NULL. Это позволяет использовать встроенные курсоры Windows. Их также часто называют стандартными курсорами. Не чувствуете ли вы, что уже читали что-то похожее?

Второй параметр — это указатель на строку, содержащую имя загружаемого курсора. Как можно видеть, в рассматриваемом примере я присваиваю этому параметру значение IDC_ARROW. Оно соответствует стандартному курсору Windows, который вы могли видеть во многих программах. Оставшиеся стандартные значения перечислены в таблице 2.4.

Таблица 2.4. Константы для стандартных курсоров

Значение Описание
IDC_APPSTRING Это курсор в форме стандартной стрелки с присоединенными к ней песочными часами. Обычно, данный курсор устанавливается, когда ваша программа занята.
IDC_ARROW Стандартный курсор Windows.
IDC_CROSS Создает курсов, выглядящий как перекрестье прицела.
IDC_HELP Этот курсор выглядит как стандартная стрелка с присоединенным к ней вопросительным знаком. Его хорошо использовать, когда пользователю предоставляется возможность задать вопрос.
IDC_IBEAM Курсор в форме буквы «I». Обычно используется в режиме ввода и редактирования текста.
IDC_NO Курсор в виде перечеркнутого круга. Его можно использовать, когда пользователь наводит курсор на область, которая не реагирует на щелчки кнопок мыши.
IDC_SIZEALL Курсор с перекрещенными стрелками. Применяется, когда пользователь изменяет размер окна или графического элемента.
IDC_SIZENESW Еще один курсор для изменения размера. В отличие от предыдущего курсора, у которого стрелки направлены во все четыре стороны, здесь стрелки направлены только на северо-восток и юго-запад.
IDC_SIZENS То же, что и предыдущий курсор, но стрелки направлены на север и на юг.
IDC_SIZENWSE То же, что и предыдущие два курсора, но стрелки направлены на северо-запад и юго-восток.
IDC_SIZEWE Еще один курсор со стрелками. В данном случае они направлены на запад и на восток.
IDC_UPARROW Курсор в виде стрелки, направленной вверх.
IDC_WAIT Курсор в виде песочных часов. Я рекомендую использовать этот курсор в тех случаях, когда ваша программа занята и пользователь может только ждать пока она не закончит работу.

После того, как вы освоились с относящейся к курсорам частью структуры данных окна, пришло время поговорить о цвете фона. Чтобы задать цвет фона вашего окна, вы просто указываете желаемый цвет в члене данных hbrBackground. Это девятый элемент структуры, и он имеет тип HBRUSH. Возможно, вы подумали, что тип HBRUSH это всего лишь дескриптор класса кисти. Но этот член данных более гибкий и позволяет задать как дескриптор кисти, которая будет использоваться, так и значение используемого цвета. Существует одно требование — задавая цвет, вы должны использовать одно из следующих значений:

  • COLOR_ACTIVECAPTION
  • COLOR_APPWORKSPACE
  • COLOR_BACKGROUND
  • COLOR_BTNFACE
  • COLOR_BTNSHADOW
  • COLOR_BTNTEXT
  • COLOR_CAPTIONTEXT
  • COLOR_GRAYTEXT
  • COLOR_HIGHLIGHT
  • COLOR_HIGHLIGHTTEXT
  • COLOR_INACTIVEBORDER
  • COLOR_INACTIVECAPTION
  • COLOR_MENU
  • COLOR_MENUTEXT
  • COLOR_SCROLLBAR
  • COLOR_WINDOW
  • COLOR_WINDOWFRAME
  • COLOR_WINDOWTEXT

В рассматриваемом примере я не использую этот метод. Но если вы захотите установить для фона один из стандартных цветов, просто замените задающую фоновый цвет строку кода примера на ту, которая приведена ниже:

wndclass.hbrBackground = (HBRUSH)COLOR_GRAYTEXT;

В результате выполнения этого кода фон окна будет окрашен в серый цвет. Вы можете заменить COLOR_GRAYTEXT на любое из перечисленных выше значений. В моем коде вместо этого используется другой метод, включающий вызов функции GetStockObject().

Функция GetStockObject() часто используется для того, чтобы получить дескриптор одной из встроенных кистей, шрифтов, палитр или перьев. Дело в том, что в Windows есть несколько предопределенных типов, которые могут быть использованы в ваших приложениях. Давайте взглянем на прототип функции:

HGDIOBJ GetStockObject(
    int fnObject
);

Если вам нравятся функции у которых только один параметр, то вот одна из них. Ее единственный параметр представляет собой целое число идентифицирующее предопределенный объект системы, который вы хотите использовать. Если вы укажете значение, соответствующее существующему объекту, то возвращенное функцией значение не будет равно нулю.

Моей программе посчастливилось использовать встроенный объект WHITE_BRUSH. Он окрашивает фон окна в белый цвет. В таблице 2.5 приведены еще несколько значений, которые можно использовать в качестве аргумента функции GetStockObject().

Таблица 2.5. Предопределенные объекты Windows

Значение Описание
BLACK_BRUSH Соответствует названию. Объект предоставляет кисть, рисующую черным цветом.
DKGRAY_BRUSH Темно-серая кисть.
GRAY_BRUSH Серая кисть.
HOLLOW_BRUSH Возможно вы смотрели фильм «Человек-невидимка»? (Если не смотрели, не волнуйтесь — все равно фильм плохой.) Эта кисть невидима для пользователя, так же как и человек-невидимка. Это означает, что при использовании данной кисти не появляется никаких цветов. Аналогичным образом действует кисть NULL_BRUSH.
LTGRAY_BRUSH Светло-серая кисть.
NULL_BRUSH То же самое, что и HOLLOW_BRUSH.
WHITE_BRUSH Белая кисть. Именно она используется в моем примере.
BLACK_PEN Черное перо. Перья не влияют на цвет фона. Вы должны использовать их для задания цвета текста.
WHITE_PEN Белое перо.
ANSI_FIXED_FONT Объект устанавливает моноширинный системный шрифт.
ANSI_VAR_FONT Объект устанавливает системный шрифт с переменной шириной символов.
DEFAULT_GUI_FONT Устанавливается заданный по умолчанию системный шрифт.
DEFAULT_PALETTE Объект установленной по умолчанию палитры.

Десятый член структуры данных WNDCLASSEX — это завершающаяся нулевым символом строка с именем lpszMenuName. Она содержит имя ресурса меню, используемого окном. В моих окнах нет меню, поэтому я присваиваю данной переменной значение NULL.

Одиннадцатый член структуры данных также является строкой, которая должна завершаться нулевым символом. Его имя — lpszClassName. Как сказано в имени, эта строка используется для задания имени класса окна. Имя класса является уникальным идентификатором типа класса. Поэтому очень важно, чтобы вы не использовали заданное здесь имя для других классов вашей программы.

Двенадцатый, и последний, член структуры WNDCLASSEX — это переменная с именем hIconSm. Она аналогична члену данных hIcon, за исключением того, что здесь задается используемый программой маленький значок. В моем примере для этого применяется уже знакомая вам функция LoadIcon().

Ну что? Мы завершили изучение структуры данных WNDCLASSEX! Теперь пришло время зарегистрировать ее.

Функция RegisterClassEx()

Сейчас мы достигли момента, когда класс окна должен быть зарегистрирован. Ниже приведен фрагмент кода, выполняющий это действие.

if(RegisterClassEx(&wndclass) == 0)
{
    // Сбой программы, выход
    exit(1);
}

Вызов функции RegisterClassEx() необходим, чтобы потом мы смогли создать окно. Эта функция регистрирует класс в системе Windows. Если класс не зарегистрирован, то вы не сможете использовать его для создания окна.

Функция очень проста. Вот ее прототип:

ATOM RegisterClassEx(
    CONST WNDCLASSEX *lpwcx
);

Первый и единственный необходимый для нее параметр является указателем на структуру данных WNDCLASSEX. Здесь все просто, — в нашем примере достаточно только передать функции значение &wndclass.

Не следует слишком волноваться из-за того, что функция возвращает значение типа ATOM. Важно только проверить равно возвращаемое функцией значение NULL или нет. Если возвращаемое функцией RegisterClassEx() значение не равно нулю, ее выполнение завершилось успешно.

Итак, класс окна зарегистрирован, и программа переходит к действительному созданию окна.

Функция CreateWindowEx()

Для создания окна используется функция CreateWindowEx(). Ее можно применять для создания дочерних, всплывающих или перекрывающихся окон. При создании окна вы указываете используемый класс, имя приложения и некоторые другие параметры. Прототип функции выглядит следующим образом:

HWND CreateWindowEx(
    DWORD dwExStyle,
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
);

Первый параметр имеет тип DWORD и называется dwExStyle. Он похож на определяющий стиль член структуры WNDCLASSEX, но задает дополнительные стили окна. Список доступных дополнительных стилей приведен в таблице 2.6.

Таблица 2.6. Дополнительные стили окна

Стиль Описание
WS_EX_ACCEPTFILES Окно может получать файлы с использованием механизма перетаскивания.
WS_EX_APPWINDOW Заставляет окно перемешаться на верхний уровень панели задач, когда оно видимо.
WS_EX_CLIENTEDGE Окно обрамлено рамкой, чтобы клиентская область выглядела углубленной.
WS_EX_CONTROLPARENT Позволяет переключаться между дочерними окнами с помощью клавиши TAB.
WS_EX_DLGMODALFRAME Окно будет обрамлено двойной рамкой.
WS_EX_LEFT Окно выровнено по левой границе. Это значение по умолчанию.
WS_EX_LEFTSCROLLBAR Для некоторых языков полосу прокрутки следует располагать слева от текста. В таких случаях и применяется этот стиль.
WS_EX_LTRREADING Отображаемый в окне текст читается слева направо. Это значение по умолчанию.
WS_EX_NOPARENTNOTIFY Подавляет сообщение WM_PARENTNOTIFY отправляемое дочерним окном родительскому при создании или уничтожении. Применяется для дочерних окон.
WS_EX_OVERLAPPEDWINDOW Комбинация флагов WS_EX_CLIENTEDGE и WS_EX_WINDOWEDGE.
WS_EX_PALETTEWINDOW Комбинация флагов WS_EX_WINDOWEDGE, WS_EX_TOOLWINDOW и WS_EX_TOPMOST.
WS_EX_RIGHT Содержимое окна выравнивается по правой границе. Это необходимо для некоторых языков.
WS_EX_RIGHTSCROLLBAR Полоса прокрутки располагается справа от клиентской части окна. Это значение по умолчанию.
WS_EX_RTLREADING В некоторых языках текст читают справа налево. Для таких языков использование данного стиля позволит системе отображать символы в окне справа налево.
WS_EX_STATICEDGE Создает окно, предназначенное для элементов, которые не будут получать входные данные от пользователя.
WS_EX_TOOLWINDOW Создает окно, выглядящее как панель инструментов.
WS_EX_TOPMOST Окно будет оставаться самым верхним на рабочем столе, независимо от того, активно оно или нет.
WS_EX_WINDOWEDGE У окна будет рамка с рельефной гранью.

В рассматриваемом примере я указываю в первом параметре дополнительный стиль WS_EX_OVERLAPPEDWINDOW. Благодаря использованию этого стиля, окно программы будет выглядеть подобно большинству приложений Windows.

Второй параметр представляет собой завершающуюся нулевым символом строку, содержащую имя класса для создаваемого окна. Все, что необходимо сделать — указать то же имя, которое вы использовали при инициализации члена lpszClassName структуры WNDCLASSEX. В моем примере я использую строку «Window Class».

Третий параметр — это еще одна завершающаяся нулевым символом строка. Вместо того, чтобы определять имя класса, она задает текст, который будет выведен в заголовке окна. Вы можете назвать свою программу как пожелаете, а я свою назвал «Create Window Example».

Четвертый параметр, dwStyle, позволяет задать различные комбинации стилей для вашего окна. Доступные стили перечислены в таблице 2.7.

Таблица 2.7. Cтили окна

Стиль Описание
WS_BORDER Окно обрамлено тонкой рамкой.
WS_CAPTION У окна есть заголовок.
WS_CHILD Окно является дочерним. Этот стиль нельзя использовать вместе со стилем WS_POPUP.
WS_CHILDWINDOW То же самое, что и WS_CHILD.
WS_CLIPCHILDREN Предотвращает рисование в тех областях окна, которые заняты дочерними окнами.
WS_CLIPSIBLINGS Используется только для дочерних окон. Если дочернее окно, для которого был установлен этот стиль перекрывается другими дочерними окнами, то при обновлении содержимого окна будет перерисовываться только та часть, которая не закрыта другими окнами.
WS_CLIPSIBLINGS Окно заблокировано и не может принимать данные от пользователя.
WS_DLGFRAME Стиль для диалоговых окон.
WS_GROUP Указывает, что данное окно является первым в группе окон. Используется для упорядочивания окон. Первое окно, у которого установлен стиль WS_GROUP начинает группу, после чего все последующие окна будут добавляться в ту же группу, пока снова не встретится окно с установленным стилем WS_GROUP (оно начнет следующую группу).
WS_HSCROLL Окно с горизонтальной полосой прокрутки.
WS_ICONIC После создания окно отображается в свернутом виде.
WS_MAXIMIZE После создания окно отображается развернутым на весь экран.
WS_MAXIMIZEBOX В заголовке окна есть кнопка для развертывания на весь экран.
WS_MINIMIZE То же самое, что и стиль WS_ICONIC.
WS_MINIMIZEBOX В заголовке окна есть кнопка для свертывания окна.
WS_OVERLAPPED Перекрывающееся окно с заголовком и рамкой.
WS_OVERLAPPEDWINDOW Окно с комбинацией стилей WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX.
WS_POPUP Стиль для всплывающих окон.
WS_POPUPWINDOW Всплывающее окно с комбинацией стилей WS_BORDER, WS_POPUP и WS_SYSMENU.
WS_SIZEBOX Окно может изменять размер.
WS_SYSMENU У окна есть системное меню, вызываемое при щелчке правой кнопкой мыши по заголовку. Чтобы этот стиль работал правильно, он должен использоваться совместно со стилем WS_CAPTION.
WS_TABSTOP Окно может стать активным при нажатии на клавишу TAB.
WS_THICKFRAME То же самое, что и стиль WS_SIZEBOX.
WS_TILED То же самое, что и стиль WS_OVERLAPPED.
WS_VISIBLE После создания окно будет видимым.
WS_VSCROLL Окно с вертикальной полосой прокрутки.

В программе CreateWindow в этом параметре я указываю стиль WS_OVERLAPPEDWINDOW. Это придает окну вид, похожий на большинство других приложений Windows.

Пятый параметр функции CreateWindowEx() — это целое число, задающее позицию окна на экране по горизонтали. Если вы создаете дочернее окно, координата отсчитывается относительно координаты по горизонтали родительского окна. Поскольку в примере нет дочерних окон, используемое значение координаты, 0, приведет к размещению окна вплотную к левой границе экрана.

Шестой параметро задает позицию окна на экране по вертикали. Подобно позиции по горизонтали, позиция по вертикали представляет координату на экране только в том случае, если окно не является дочерним. Для дочернего окна это значение задает смещение относительно позиции по вертикали родительского окна. Чтобы лучше понять эту концепцию взгляните на рис. 2.11.

Рис. 2.11. Координаты окна определяют его позицию в зависимости от того, является окно дочерним или нет

Рис. 2.11. Координаты окна определяют его позицию в зависимости от того, является окно дочерним или нет

Значения координат обоих окон на рис. 2.11 равны (10,10). Большее окно является родительским, поэтому его координаты (10,10) задают размещение окна почти вплотную к левой и верхней границам экрана. Меньшее окно является дочерним. Поэтому его координаты являются смещениями относительно родительского окна. Это означает, что координаты (10,10) в действительности представляют (parentx+10,parenty+10) или (20,20) в системе координат экрана.

Седьмой параметр создающей окно функции задает ширину окна в пикселах. В нашем примере ширина окна равна 320 пикселам.

Восьмой параметр задает высоту окна в пикселах. В примере высота окна равна 200 пикселам.

Девятый параметр функции указывает родителя данного окна. Он полезен, когда создается приложение с несколькими окнами. Поскольку в рассматриваемом примере есть только одно главное окно, данному параметру присваивается значение NULL чтобы указать, что у окна нет родителя.

Десятый параметр используется для задания дескриптора связанного с окном меню. В примере нет никаких меню, поэтому данному параметру присваивается значение NULL.

Одиннадцатый параметр используется для задания дескриптора экземпляра модуля. В этом параметре вы передаете дескриптор экземпляра, полученный функцией WinMain() в одном из ее параметров.

Последний параметр используется для указания дополнительных данных создаваемого окна. Мне очень редко приходилось использовать этот параметр, поэтому в большинстве приложений я присваиваю ему значение NULL.

Ничего себе! Вы проделали почти всю работу, необходимую для отображения окна на экране. На рис. 2.12 показан ход выполнения программы до настоящего момента.

Рис. 2.12. Ход выполнения кода приложения до вызова функции CreateWindowEx()

Рис. 2.12. Ход выполнения кода приложения до вызова функции CreateWindowEx()

Функция ShowWindow()

Верите или нет, но факт создания окна не приводит к его отображению. (Эти парни из Microsoft иногда такие странные!) Чтобы окно было действительно выведено на экран вы должны вызвать функцию ShowWindow(). К счастью, она достаточно прямолинейна. Вот ее прототип:

BOOL ShowWindow(
    HWND hWnd,
    int nCmdShow
);

Первый параметр задает дескриптор отображаемого окна. Это действительно просто, поскольку дескриптор уже подготовлен функцией, создавшей окно. Взгляните на пример, чтобы увидеть как я передаю дескриптор, возвращенный функцией CreateWindow().

Второй параметр — это целое число, определяющее как будет отображаться окно. Вот и пришло время для еще одной таблицы. Значения, которые можно использовать в этом параметре перечислены в таблице 2.8.

Таблица 2.8. Значения для функции ShowWindow()

Значение Описание
SW_HIDE Окно скрыто и затем окно активируется.
SW_MAXIMIZE Окно развернуто на весь экран.
SW_MINIMIZE Окно свернуто и затем окно активируется.
SW_RESTORE Восстанавливает окно из свернутого или развернутого состояния. Это полезно, если требуется вернуть окну его первоначальные размеры и местоположение.
SW_SHOW Активирует окно и отображает его.
SW_SHOWMAXIMIZED Окно активируется и отображается развернутым на весь экран.
SW_SHOWMINIMIZED Окно активируется и отображается свернутым.
SW_SHOWNA Окно отображается в его текущем состоянии. Не оказывает никакого влияния если окно в данный момент активно.
SW_SHOWNORMAL Отображает окно в его нормальном состоянии. Это значение используется, когда функция ShowWindow() вызывается в первый раз.

Хотя есть много значений, которые вы можете указать во втором параметре, простейший способ заключается в передаче значения целочисленной переменной iCmdShow, которая является одним из параметров функции WinMain(). Именно так я и поступаю в коде примера.

Итак, ваша программа полностью завершила свою основную задачу — отображение окна. Однако, она пока не может принимать никаких входных данных. Здесь в игру вступает код цикла обработки сообщений.

Получение сообщений функцией GetMessage()

Чтобы приложения Windows знали когда их окна закрываются, перемещаются или изменяют размеры, они должны принимать сообщения Windows. В общем случае ваши приложения для Windows должны всегда иметь цикл сообщений. Вспомните сведения о событиях и цикле сообщений, которые я приводил в начале главы. В примере я использую простейший метод проверки наличия сообщений и обработки любых обнаруженных сообщений.

Чтобы проверить наличие ожидающих обработки сообщений вызывается функция GetMessage(). Вот ее прототип:

BOOL GetMessage(
    LPMSG lpMsg,
    HWND hWnd,
    UINT wMsgFilterMin,
    UINT wMsgFilterMax
);

В первом параметре функция ожидает указатель на объект MSG. Структура данных MSG содержит всю информацию о любом обнаруженном сообщении.

Второй параметр указывает для какого окна проводится проверка наличия сообщений. Он необходим потому, что ваша программа может управлять несколькими окнами. В рассматриваемом примере есть только одно окно, поэтому я просто передаю дескриптор окна, созданного функцией CreateWindow().

Третий параметр задает нижнюю границу кодов получаемых сообщений. Мы хотим получать все сообщения, поэтому присваиваем данному параметру значение 0.

Четвертый параметр позволяет задать верхнюю границу кодов получаемых сообщений. Поскольку мы хотим получать все сообщения, значение этого параметра также равно 0.

Трансляция сообщений функцией TranslateMessage()

Перед тем, как отправить сообщение в вашу очередь сообщений, вы должны транслировать его в символьные данные. Это делает функция TranslateMessage(). Для нее требуется единственный параметр — указатель на транслируемое сообщение. В примере я передаю функции адрес переменной, содержащей сообщение.

После того, как сообщение транслировано в символьные данные, вы можете поместить его в очередь сообщений с помощью функции DispatchMessage().

Помещение сообщений в очередь функцией DispatchMessage()

Подобно функции TranslateMessage(), функция DispatchMessage() требует единственный параметр. Цель этой функции — отправить прошедшее трансляцию сообщение в очередь сообщений программы. После вызова этой функции сообщение попадает в функцию обработки сообщений.

Последняя строка кода функции WinMain() возвращает значение wParam последнего сообщения Windows извлеченного функцией получения сообщений. Как говорил поросенок Порки, «Вот и все, ребята!».

Функция обработки сообщений

Вы вероятно задаетесь вопросом, где обрабатываются сообщения. Когда функция DispatchMessage() отправляет сообщения, они направляются функции обработки сообщений, указанной в классе окна. В рассматриваемом примере все сообщения отправляются написанной мной функции fnMessageProcessor().

Взгляните на код функции обработки сообщений, и вы увидите, что она представляет собой одну инструкцию switch средних размеров. Это вызвано тем, что единственная задача данной функции — перебрать все типы сообщений, которые интересуют нас и проверить, соответствует ли полученное сообщение одному из них.

Мой пример проверяет сообщения WM_CREATE, WM_DESTROY и WM_PAINT. Дополнительная работа производится только для сообщения WM_DESTROY.

Когда приложение получает сообщение WM_DESTROY, оно вызывает функцию PostQuitMessage(), которая завершает работу программы Windows. Это происходит, когда вы завершаете работу, щелкнув по кнопке закрытия окна.

Если полученное сообщение не соответствует ни одному из проверяемых программой, оно возвращается функции WinMain() с помощью функции DefWindowProc(). Это стандартная практика, поэтому я не рекомендую пытаться изменить ее.

Рассматриваемый пример проверяет лишь несколько типов сообщений, но примеры, встечающиеся дальше в этой книге проверяют гораздо больше сообщений. Помните об этом, когда будете просматривать код последующих примеров — вы можете столкнуться с абсолютно незнакомыми типами сообщений.

Компиляция и выполнение кода

Итак, ваша программа целиком введена и вы готовы идти дальше. Чтобы скомпилировать программу в Visual C++ 6.0 вам необходимо сделать активным окно с компилируемой программой и нажать клавишу F7. Если все закончится успешнно, то в окне с выходными данными компилятора вы увидите следующий текст:

---------Configuration: CreateWindow - Win32 Debug------------
Compiling...
CreateWindow.cpp
Linking...
CreateWindow.exe - 0 error(s), 0 warning(s)

Если во время компиляции будут обнаружены какие-либо ошибки, заново проверьте код и убедитесь, что все набрано правильно. Когда будут исправлены все ошибки, вы сможете запустить программу, нажав комбинацию клавиш Ctrl+F5. В результате начнет выполняться программа активного проекта. На рис. 2.13 показано, как должна выглядеть работающая программа рассматриваемого примера.

Рис. 2.13. Результат выполнения программы CreateWindow

Рис. 2.13. Результат выполнения программы CreateWindow

Сайт управляется системой uCoz

Понравилась статья? Поделить с друзьями:
  • Написано подготовка windows не выключайте компьютер бесконечная загрузка
  • Написание скриптов на python для windows
  • Написание приложения для ios на windows
  • Написание bat файлов для windows 10
  • Напечатать несколько фото на одном листе windows 10