Как писать программы на c для windows

C++ Documentation. Contribute to MicrosoftDocs/cpp-docs development by creating an account on GitHub.
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

Introduction: Your First Program in C (For Windows Users)

Programming can be very challenging. Learning a programming language is not something that’s going to happen overnight. It will take many frustrating hours to get familiar with the nuances of a programming language and learn how to properly “talk” a computer into doing what you want. Although I’ve been programming for more than a couple years now there are still times when I feel like pulling my hair out in frustration. But it’s also a great source of pride. The most important thing to remember is not to get discouraged. If this is something that you want to learn about there is no end to available resources online, and it’s something you can get really good at from the comfort of your desk chair.

I wrote this instructable because I know how difficult and frustrating getting started can be. So I decided to focus on what the bare minimum required to get up and running so that you can have a working program of your own to tinker around with. This instructable was written for windows computers, so unfortunately if you us something else this won’t apply.

What exactly is programming? Programming at it’s most basic is the process of creating and implementing a set of instructions for the computer. This is accomplished by utilizing a set of standardized conventions known as a programming language which functions in much the same way as a language for people does. Originally programming computers was very difficult, as what we would consider a programming language didn’t exist, and instead programmers would have to give the instructions to the computer the only way it could process them, which was as a string of 1s and 0s which the computer could then interpret. Over time, programmers built up the set of tools they could utilize so that programs would be much more easy on people to write.

However, a programming language itself is too abstract for computers to understand and requires the usage of a compiler which basically translates the code written in the programming language to something the computer can understand. The compiler then makes the code into an executable, or something that you can run and perform some kind of action. The very first real step will be installing your very own C compiler. Let’s get started!

Step 1: What’s Required?

THINGS YOU’LL NEED AHEAD OF TIME:
— You
— A computer (with windows)

And that’s it! Gather yourself, a working computer (presumably the one that you’re on) and for the next step we’ll install the software you need to interpret your code.

Step 2: Installing GCC

DISCLAMER: This step may be the most confusing/difficult.

http://gcc.gnu.org/install/

The installation instructions there are pretty in depth. To just download it, here is one link:

http://www.netgull.com/gcc/releases/gcc-4.8.1/

Download the 106mB file that ends with «tar.gz». Tar.gz is just a different kind of compression than say «.zip». Decompress the file with something like winzip or 7zip and follow the installation instructions at the gcc website.

Once the compiler is installed (and you’ve verified it with step 3), if you’d like to see a quick video run down of all the steps involved simply skip ahead to step 10. Otherwise follow along!

Step 3: Open Your Command Prompt

METHOD 1 FOR VISTA OR 7

Click on your start menu. Where you see the search bar type «cmd» with no quotes and press enter. An all black window should appear with white text that looks very dated and ominous. A picture of what it should look like is included with this step (see first picture). If your start menu doesn’t look like the one in the second picture then there’s a second method included below.

METHOD 2 WITH THE TASK MANAGER

Open the task manager by pressing either CTRL + SHIFT + ESC or CTRL + ALT + DELETE (May require you to click a button that  says «Task Manager»). Look at the top of the window that appeared and look for «Run» and press it. If it’s not visible click
File>Run. A new, smaller window should appear with just a text field. Type in «cmd» with no quotes.

Once the command prompt is open move onto the next step!

Step 4: Verifying You Have GCC

Switch to the command prompt window. To verify that GCC is in fact installed simply type «GCC» without quotes and hit enter. If the message:

‘gcc’ is not recognized as an internal or external command, operable program or batch file

appears, that means that the GNU C Compiler didn’t install properly and step 3 will have to be repeated or troubleshooted. If the following message (as pictured):

gcc: fatal error: no input files
compilation terminated.

appears that’s good! It means that all the extra software needed was installed.

Step 5: Opening a New Text File

Open a new text document for putting code into. The easiest way to accomplish this is by right clicking the desktop and going to New>Text Document as pictured for this step. Although after the second step this will leave an empty, unused text document on your desktop. Another way is to just simply open notepad. You can accomplish this by going to: 

Start>All Programs>Accessories>Notepad

Once you have a new text document open, proceed to the next step!

Step 6: CODING

In your newly opened text file, copy and paste (or type out) the following bold text into the text file.

#include <stdio.h>

void main(){

printf(«Hello World!»);

}

You can see an example in the picture for this step. Once that’s done, click File>Save As. This part is important. You can name it whatever you like (with no spaces), but set the file type to «all files» and to end your program with the extension «.c» otherwise the compiler won’t be sure what to make of it. When this program is compiled and ready to run, it will print out the sentence «Hello World!» into the command prompt window. When the source code file is saved proceed to the next step.

Step 7: Finding Your File

Switch back to the ominous looking command prompt window. There are a couple of commands that you will need to know in order to successfully locate your newly created text file.

dir
cd [directory]
cd..

dir stands for «directory». In the command prompt window you’ll have something that looks like «C:UsersBoone» which is the «working directory». Typing in dir and pressing enter will list all of the files that are located in your working directory. Which brings us to the next command cd,  which stands for «change directory». Typing cd desktop and pressing enter will bring you to where your «HelloWorld.c» file is stored (if you stored it on the Desktop like me). You can verify if it’s there by typing dir.

cd.. is the command used for moving back a folder. So if my working directer were «C:UsersBooneDesktop», by typing cd.. I would move back into «C:UsersBoone». If you need help figuring out where exactly your «HelloWorld.c» file is stored, simply right click on the file’s icon on the desktop and click on «Properties» (see first picture). The location of the file will be written next to «Location:» Once you’ve navigated your working directory to where your «HelloWorld.c» program is (HelloWorld.c shows up when you type dir. See second picture) you can move on to the next step. 

Step 8: Compiling

IF YOU DON’T CARE WHAT YOUR PROGRAM IS NAMED
If you don’t really mind what your program is going to be name, simply type:

gcc HelloWorld.c

into the command prompt window. If any errors appear, open the file back up in notepad to see if everything was typed/copied correctly, or that it was saved the right way (type set to all files with the «.c» file extension). When you compile your code this way it gets named «a.exe» by default.

IF YOU WANT TO NAME YOUR PROGRAM
Although there’s a bit more going on between the steps involved in this process, being able to name your program involves a little bit more than the previous step. Instead type:

gcc -c HelloWorld.c

The compiler will produce what is known as an object file with a «.o» extension on it. This step is necessary for making programs with multiple files that go into them, but for now we’ll just worry about naming your program. Next type:

gcc -o HelloWorld HelloWorld.o

The «HelloWorld» portion is simply what the program is going to be named, and everything after it are the constituent files. You can name it whatever you like, however spaces aren’t allowed. The pictures included for this step goes through both options. Once this step is complete you’ve successfully compiled your code and can move onto the next step!

Step 9: Hello World!

Now it’s time to run your program for the very first time!

If you missed it on step 6 (probably because you were so excited to get your program up and running) what your program will actually do is print out the sentence «Hello World» into the command prompt window. If you compiled your program in the last step using gcc HelloWorld.c all you’ll have to type to run your program is the letter «a». If you cared about what you were naming your program, simply type out the name of your program (I compiled my program in the example as «HelloWorld»). See the attached pictures for what to expect.

Step 10: Video Demonstration of the Whole Process

Be the First to Share

Recommendations

Introduction: Your First Program in C (For Windows Users)

Programming can be very challenging. Learning a programming language is not something that’s going to happen overnight. It will take many frustrating hours to get familiar with the nuances of a programming language and learn how to properly “talk” a computer into doing what you want. Although I’ve been programming for more than a couple years now there are still times when I feel like pulling my hair out in frustration. But it’s also a great source of pride. The most important thing to remember is not to get discouraged. If this is something that you want to learn about there is no end to available resources online, and it’s something you can get really good at from the comfort of your desk chair.

I wrote this instructable because I know how difficult and frustrating getting started can be. So I decided to focus on what the bare minimum required to get up and running so that you can have a working program of your own to tinker around with. This instructable was written for windows computers, so unfortunately if you us something else this won’t apply.

What exactly is programming? Programming at it’s most basic is the process of creating and implementing a set of instructions for the computer. This is accomplished by utilizing a set of standardized conventions known as a programming language which functions in much the same way as a language for people does. Originally programming computers was very difficult, as what we would consider a programming language didn’t exist, and instead programmers would have to give the instructions to the computer the only way it could process them, which was as a string of 1s and 0s which the computer could then interpret. Over time, programmers built up the set of tools they could utilize so that programs would be much more easy on people to write.

However, a programming language itself is too abstract for computers to understand and requires the usage of a compiler which basically translates the code written in the programming language to something the computer can understand. The compiler then makes the code into an executable, or something that you can run and perform some kind of action. The very first real step will be installing your very own C compiler. Let’s get started!

Step 1: What’s Required?

THINGS YOU’LL NEED AHEAD OF TIME:
— You
— A computer (with windows)

And that’s it! Gather yourself, a working computer (presumably the one that you’re on) and for the next step we’ll install the software you need to interpret your code.

Step 2: Installing GCC

DISCLAMER: This step may be the most confusing/difficult.

http://gcc.gnu.org/install/

The installation instructions there are pretty in depth. To just download it, here is one link:

http://www.netgull.com/gcc/releases/gcc-4.8.1/

Download the 106mB file that ends with «tar.gz». Tar.gz is just a different kind of compression than say «.zip». Decompress the file with something like winzip or 7zip and follow the installation instructions at the gcc website.

Once the compiler is installed (and you’ve verified it with step 3), if you’d like to see a quick video run down of all the steps involved simply skip ahead to step 10. Otherwise follow along!

Step 3: Open Your Command Prompt

METHOD 1 FOR VISTA OR 7

Click on your start menu. Where you see the search bar type «cmd» with no quotes and press enter. An all black window should appear with white text that looks very dated and ominous. A picture of what it should look like is included with this step (see first picture). If your start menu doesn’t look like the one in the second picture then there’s a second method included below.

METHOD 2 WITH THE TASK MANAGER

Open the task manager by pressing either CTRL + SHIFT + ESC or CTRL + ALT + DELETE (May require you to click a button that  says «Task Manager»). Look at the top of the window that appeared and look for «Run» and press it. If it’s not visible click
File>Run. A new, smaller window should appear with just a text field. Type in «cmd» with no quotes.

Once the command prompt is open move onto the next step!

Step 4: Verifying You Have GCC

Switch to the command prompt window. To verify that GCC is in fact installed simply type «GCC» without quotes and hit enter. If the message:

‘gcc’ is not recognized as an internal or external command, operable program or batch file

appears, that means that the GNU C Compiler didn’t install properly and step 3 will have to be repeated or troubleshooted. If the following message (as pictured):

gcc: fatal error: no input files
compilation terminated.

appears that’s good! It means that all the extra software needed was installed.

Step 5: Opening a New Text File

Open a new text document for putting code into. The easiest way to accomplish this is by right clicking the desktop and going to New>Text Document as pictured for this step. Although after the second step this will leave an empty, unused text document on your desktop. Another way is to just simply open notepad. You can accomplish this by going to: 

Start>All Programs>Accessories>Notepad

Once you have a new text document open, proceed to the next step!

Step 6: CODING

In your newly opened text file, copy and paste (or type out) the following bold text into the text file.

#include <stdio.h>

void main(){

printf(«Hello World!»);

}

You can see an example in the picture for this step. Once that’s done, click File>Save As. This part is important. You can name it whatever you like (with no spaces), but set the file type to «all files» and to end your program with the extension «.c» otherwise the compiler won’t be sure what to make of it. When this program is compiled and ready to run, it will print out the sentence «Hello World!» into the command prompt window. When the source code file is saved proceed to the next step.

Step 7: Finding Your File

Switch back to the ominous looking command prompt window. There are a couple of commands that you will need to know in order to successfully locate your newly created text file.

dir
cd [directory]
cd..

dir stands for «directory». In the command prompt window you’ll have something that looks like «C:UsersBoone» which is the «working directory». Typing in dir and pressing enter will list all of the files that are located in your working directory. Which brings us to the next command cd,  which stands for «change directory». Typing cd desktop and pressing enter will bring you to where your «HelloWorld.c» file is stored (if you stored it on the Desktop like me). You can verify if it’s there by typing dir.

cd.. is the command used for moving back a folder. So if my working directer were «C:UsersBooneDesktop», by typing cd.. I would move back into «C:UsersBoone». If you need help figuring out where exactly your «HelloWorld.c» file is stored, simply right click on the file’s icon on the desktop and click on «Properties» (see first picture). The location of the file will be written next to «Location:» Once you’ve navigated your working directory to where your «HelloWorld.c» program is (HelloWorld.c shows up when you type dir. See second picture) you can move on to the next step. 

Step 8: Compiling

IF YOU DON’T CARE WHAT YOUR PROGRAM IS NAMED
If you don’t really mind what your program is going to be name, simply type:

gcc HelloWorld.c

into the command prompt window. If any errors appear, open the file back up in notepad to see if everything was typed/copied correctly, or that it was saved the right way (type set to all files with the «.c» file extension). When you compile your code this way it gets named «a.exe» by default.

IF YOU WANT TO NAME YOUR PROGRAM
Although there’s a bit more going on between the steps involved in this process, being able to name your program involves a little bit more than the previous step. Instead type:

gcc -c HelloWorld.c

The compiler will produce what is known as an object file with a «.o» extension on it. This step is necessary for making programs with multiple files that go into them, but for now we’ll just worry about naming your program. Next type:

gcc -o HelloWorld HelloWorld.o

The «HelloWorld» portion is simply what the program is going to be named, and everything after it are the constituent files. You can name it whatever you like, however spaces aren’t allowed. The pictures included for this step goes through both options. Once this step is complete you’ve successfully compiled your code and can move onto the next step!

Step 9: Hello World!

Now it’s time to run your program for the very first time!

If you missed it on step 6 (probably because you were so excited to get your program up and running) what your program will actually do is print out the sentence «Hello World» into the command prompt window. If you compiled your program in the last step using gcc HelloWorld.c all you’ll have to type to run your program is the letter «a». If you cared about what you were naming your program, simply type out the name of your program (I compiled my program in the example as «HelloWorld»). See the attached pictures for what to expect.

Step 10: Video Demonstration of the Whole Process

Be the First to Share

Recommendations

Содержание

  • Создание приложений (С++, Win32)
    • Введение
    • Программирование под Windows
      • Стандарт оформления кода (Coding convention)
        • Венгерская нотация (Hungarian Notation)
      • Работа со «странными» типами данных Windows
    • Базовое приложение Windows
      • Консольное базовое приложение
      • Оконное базовое приложение (WinAPI)
      • Пример простейшего оконного приложения
        • Создаём Проект WinTest01
        • Готовим Проект WinTest01 к компиляции
        • Компилируем проект WinTest01
        • Исследуем код WinMain.cpp
        • Заголовочные файлы и директива include
        • Функция WinMain
        • Заполняем структуру оконного класса
        • Регистрация оконного класса
        • Создание окна
        • Конвейер (цикл) выборки сообщений (Message pump)
        • Оконная процедура и обработка сообщений
        • Код завершения работы приложения
      • Пишем текст в окне
        • Изменения в WinMain.cpp
    • Сообщения Windows (теория)
    • Распаковщики сообщений
    • Дополнительные возможности программирования Windows
      • Всплывающее (модальное) окно сообщения (Message Box)
      • Диалоговые окна (Dialog Boxes)
      • Двоичные ресурсы Windows (Binary resources)
      • Потоки и мультипоточность (Threads and Multithreading).
      • Критические секции (Critical sections)
    • Итоги
    • Источники

Введение

По данной теме написана не одна сотня книг (многие из которых на русском языке). А так как данная статья не претендует на полноту изложения, настоятельно рекомендуем прочесть хотя бы пару из них. Не факт, что всё усвоишь, но основные моменты всё равно запомнишь.
В 80-90-х годах XX века игрокодеры полагались только на свои навыки программирования под MS DOS (если игра разрабатывалась под эту ОС), стремясь любыми путями получить плавное движение игровой анимации.1 В те времена Windows являлась операционной системой, ориентированной на бизнес-приложения. Но никак не на игры. Ситуация изменилась с выходом ОС MS Windows 95 и появлением первых версий DirectX.
Под платформой Win32 подразумеваются все 32-разрядные (32-битные) операционные системы семейства MS Windows (9x/NT/2000/XP/Vista/7).

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

Для создания приложений под ОС MS Windows необходимо:

  • Компьютер с установленной ОС Windows.
  • Компилятор языка.

В нашем случае C++. Удобнее использовать интегрированную среду разработки (ИСР, IDE), в состав которой входит нужный компилятор. Хороший пример — MS Visual C++ 2010. О том, где её взять и как установить читай здесь: MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

  • Windows SDK (Software Development Kit).

Обычно всего устанавливается в наборе с MS Visual C++. Содержит необходимые заголовочные файлы и библиотеки для создания Windows-приложений. Компания Microsoft прилагает немало усилий для того, чтобы ОС Windows была удобной и стабильной платформой, доступной для программирования. Собственно для этого и был придуман Microsoft SDK, представляющий собой набор стандартов, на основе которых должны создаваться Windows-приложения. Одним из таких стандартов является стандарт оформления кода (Coding covention).

Стандарт оформления кода (Coding convention)

…от Microsoft весьма объёмен. Не нужно во всём строго ему следовать. Но рациональное зерно в нём есть. Цель данного стандарта — приучить программеров к единообразному объявлению переменных, однотипному написанию имён функций, указанию типов… Стандарт призван повысить читабельность исходного кода, который в крупных компаниях разрабатывается (и, следовательно, должен быть понят) группой программеров.

Венгерская нотация (Hungarian Notation)

Соглашения по написанию кода от Microsoft включают в себя свод указаний по объявлению переменных, названный впоследствии Венгерской нотацией (в честь своего венгерского создателя Чарльза Симоний (Charles Simonyi)). Этот без преувеличения гениальный метод предлагает обозначать имена переменных специальными префиксами, отражающими соответствующие типы данных. В литературе по Windows-программингу авторы стараются придерживаться Венгерской нотации. Но, тем не менее, строго ей не придерживаются. Вот далеко не полный список данных префиксов:

Префикс Тип данных Пример
f Boolean BOOL fFlag;
b Byte char bVariable;
dw Double word (long) long dwValue;
h 32-bit идентификатор экземпляра (handle) long hWindow;
i Integer int iNumber;
p Pointer void *pData;
I Interface IUnknown *IInterface;
g_ Global char g_GlobalVariable;
m_ Member short m_MemberData;

Работа со «странными» типами данных Windows

Пожалуй самое сложное в изучении Windows-программировании — это усвоение специфических типов данных, встречающихся в каждом оконном приложении.2 Один из наиболее часто встречающихся типов — handle, представляющий собой уникальный номер, присваиваемый окнам либо его объектам (значок, кнопка, заголовок и т.д.). Хэндлы очень важны, т.к. окна и его объекты часто перемещаются внутри системной памяти Windows, т.е. их адрес постоянно изменяется. Хэндл предоставляет объекту постоянную ссылку, не зависящую от реального расположения объекта в памяти. Хэндлы используются повсеместно в Win32 API.
Помимо этого Win32 API предоставляет множество уникальных типов данных (data types), которые на первый взгляд вообще не похожи на типы данных. Их главной особенностью является то, что их имена всегда пишутся заглавными (большими) буквами.
Поэтому их легко найти в исходном коде. Пример: RECT, HWND. Тип HWND представляет собой хэндл (handle) окна.
Для того, чтобы начать работать типами данных Win32, в исходный код необходимо подключить специальный заголовочный файл:

(Идёт в наборе с любой IDE под Windows.)
Наиболее часто применяемые типы данных Windows:

Макрос Описание
BOOL Булево значение (TRUE или FALSE).
BYTE 8-битное целое число без знака (unsigned int).
DWORD 32-битное целое число без знака (unsigned long).
LONG 32-битное целое число со знаком (signed long).
LPARAM 32-битное значение, передаваемое в качестве параметра в оконную процедуру (window procedure) или функцию обратного вызова (callback function).
WPARAM Значение, передаваемое в качестве параметра в оконную процедуру (window procedure) или функцию обратного вызова (callback function).
LPCSTR 32-битный указатель на неизменяемую строку символов (constant character string).
LPSTR 32-битный указатель на строку символов (character string).
LPVOID 32-битный указатель на неуказанный тип (unspecified type).
LRESULT 32-битное значение, возвращаемое из оконной процедуры (window procedure) или функции обратного вызова (callback function).
UINT 32-битное беззнаковое целое в Win32 (unsigned integer).
WNDPROC 32-битный указатель на оконную процедуру (window procedure).
WORD 16-битное беззнаковое целое (unsigned short).

Базовое приложение Windows

  • Проект, содержащий в себе минимальную, необходимую для работы (простого отображения окна) функциональность.
  • Имеет минимальный объём исходного кода. Часто весь исходный код содержится в единственном файле (традиционно он называется Main.cpp или WinMain.cpp).
  • Служит «отправной точкой» для создания любых других приложений (включая компьютерные игры) путём добавления в него различных функций, переменных, классов и т.д.
  • Иногда в литературе его также называют шаблоном Windows-приложения.

Для создания базового приложения в MS Visual C++ (все версии) существуют специальные мастера (wizards). Для вызова списка этих мастеров достаточно создать новый проект, выбрав в Главном меню IDE Файл->Создать->Проект. После ввода имени Проекта и нажатия <ОК>, Мастер приложений последовательно покажет окно приветствия и окно Параметры приложения, в котором (для автоматической генерации исходного кода) в разделе Дополнительные параметры необходимо снять флажок с пункта «Пустой проект». Вся процедура (на примере создания консольного приложения) неплохо описана здесь(external link). После этого созданный Проект можно просмотреть, отредактировать или сразу отправить на компиляцию (F5).

Закрыть

noteПримечание

Генерируемый MS Visual C++ исходный код базового приложения далёк от идеала (он состоит из 4 и более файлов, включает в себя дополнительные аргументы, «крайне необходимые» по мнению разработчиков IDE) и не является единственно правильным. Более того, исходный код базового приложения сильно разнится в разных изданиях и учебных курсах. Все эти версии, в общем-то, равноценны (по быстродействию, эффективности и т.д.). Главное здесь — чтобы код был минимален по объёму и доступен для понимания даже начинающим программистам. Та же ситуация с оконными базовыми приложениями (только вариантов кода здесь значительно больше). Во всех случаях рекомендуется начинать новый проект с «Пустого проекта» и потом уже самостоятельно добавлять в него необходимые исходные и заголовочные файлы.

Все приложения Windows делятся на 2 основных типа:

  • Консольные приложения(external link). Внешне они выглядят как программы с текстовым интерфейсом, но способны обращаться к большинству функций Windows. Практически не изменились со времён MS-DOS: всё те же серые строки символов на чёрном фоне. В OS Windows консольные приложения обычно запускают из Командной строки (Пуск->Все программы->Стандартные->Командная строка), которая тоже является консольным приложением. В MS Windows 98 Командная строка называется Сеанс MS-DOS.
  • Оконные приложения. Собственно, для чего и задумывалась MS Windows, начиная с её первой версии. Окна могут изменять свои размеры, приобретать и терять фокус, перекрывать друг друга. Также они имеют стандартные атрибуты: кнопки «Свернуть», «Развернуть», «Закрыть»; строку заголовка; строку состояния и т.д. Все современные игры для ОС MS Windows также являются оконными приложениями (как правило, развёрнутыми на весь экран).

Консольное базовое приложение

Исходный код простейшего консольного приложения содержится в одном файле (в нашем случае это Main.cpp) и выглядит так:

Листинг 1. Main.cpp

int main()	// Главная функция программы

{
 return 0;	// Функция типа int обязана вернуть какое-либо значение
}

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

  • Стартуй MS Visual C++ 2010 (подойдут и другие версии этой IDE) и создай новый проект, выбрав в Главном меню IDE Файл->Создать->Проект.

О том, где взять и как установить MS Visual C++ 2010 Express читай в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

  • В окне «Создать проект» выбираем мастер: «Консольное приложение Win32», в строке «Имя» пишем название проекта (в нашем случае Test01).

Строки «Расположение» и «Имя Решения» заполняются автоматически (при необходимости изменяем).

  • Жмём «OK», «Далее».

Image

  • На странице «Параметры приложения»: оставляем «Консольное приложение» и отмечаем пункт «Пустой проект».
  • Жмём «Готово».

Image
Проект создан. Так как это «Пустой проект», он не содержит в себе никаких файлов. В левой части расположен «Обозреватель решений». Если его нет, в главном меню выбираем: Вид->Другие окна->Обозреватель решений. Или комбинация горячих клавиш Ctrl+Alt+L. В Обозревателе решений видна древовидная структура Проектов, входящих в данное Решение. Чуть ниже названия Проекта видим специально заготовленные папки (в MSVC++2010 они называются «фильтры») для файлов Проекта:
Таблица 1

Внешние зависимости Чаще всего здесь размещаются заголовочные файлы различных сторонних библиотек. Причём, они представлены здесь в виде ссылок. «Физически» файлы, как правило, находятся за пределами каталога Проекта и не являются его частью. Содержимое каталога «Внешние зависимости» (если внимательно посмотреть, то он тоже является своеобразным ярлыком или ссылкой) генерируется автоматически в процессе линковки и поиска так называемых внешних «зависимых» библиотек.
Заголовочные файлы Содержит заголовочные файлы Проекта (.h).
Файлы исходного кода Содержит исходные файлы Проекта (.cpp).
Файлы ресурсов Содержит так называемые «бинарные» ресурсы (формы, иконки, звуки и т.д.).

  • Щёлкни правой кнопкой мыши по фильтру «Файлы исходного кода» -> во всплывающем меню выбираем: Добавить -> Создать элемент.

Image

  • В окне «Добавление нового элемента» отмечаем «Файл C++ (.cpp)» и в строке «Имя» вводим Main.cpp. Жмём «Добавить».

Image
Добавленный файл Main.cpp сразу же открывается в редакторе кода.

  • Последовательно вводим все строки исходного кода из Листинга 1, представленного чуть выше (без номеров строк на сером фоне). Настоятельно рекомендуется вводить код вручную (не с помощью копировать/вставить), — только так можно научиться программировать (лучше запоминаешь структуру исходного кода, ключевые слова C++ и, конечно, привыкаешь ставить символ «;» в конце каждой строки).
  • Запускаем компиляцию (кнопка с зелёным треугольником на Панели инструментов или <F5> на клавиатуре).

Image
Скомпилированная программа при запуске в среде Windows на долю секунды покажет чёрное окно консоли и сразу закроется.
Немного изменим нашу программу, добавив возможность выводить на экран текстовое сообщение:

Листинг 2. Main.cpp

// Базовое консольное приложение Windows
// Выводит строку текста на экран

#include <iostream>	// Подключаем заголовочный файл iostream.h из недр MS Visual C++.
                       //Без него функция std::cout не будет работать.
int main()	// Главная функция программы
{
 std::cout << "Hello, world!"; // выводим сообщение на экран
 return 0;	// Функция типа int обязана вернуть какое-либо значение
}

Комментарии при компилировании отбрасываются, так что, в принципе, они могут быть любыми. Комментируй всё, что только можно в своём коде. И ты сам, и твои последователи скажут тебе потом спасибо.

  • Внеси в Main.cpp код из Листинга 2 и снова скомпилируй Проект.

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

  • Пуск->Все программы->Стандартные->Командная строка.
  • Проводником открываем каталог с исполняемым (Test01.exe) файлом программы.

В нашем случае (Win7 x64) расположен по пути: С:Users<Имя пользователя>DocumentsVisual Studio 2010ProjectsTest01Debug .

  • Перетащи мышью Test01.exe в открытое окно Командной строки и нажми <Enter>.

Image
В итоге: Консольные приложения очень просты в программировании, но не стоит их недооценивать. Вывод текста на экран это лишь верхушка айсберга тех возможностей, которыми они обладают. Консольные приложения могут содержать в себе функции, классы, переменные. Их часто используют в математических расчётах и как программное «ядро», в дополнение к которому создают оконное приложение, которое пользуется его функционалом.

Оконное базовое приложение (WinAPI)

Вся суть Windows-программирования заключается в двух шагах:

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

Исходный код простейшего оконного приложения:

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

Что мы и сделаем. Один из вариантов исходного кода выглядит так (реально компилируется в MSVC++2010 при указании в настройках Проекта пункта «Набор символов: Использовать многобайтовую кодировку»):
Листинг 3

WinMain.cpp

// Файл: WinMain.cpp
// Описание: Шаблон оконного приложения Windows. Простейшее (базовое) приложение Windows,
// выводящее на экран окно.
// www.igrocoder.ru 2015
// При использовании просьба указывать ссылку на источник.

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>

// Прототип функции WinMain
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int nCmdShow); 

// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения

	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;	// Поочерёдный доступ к контексту устройства (Device Context)
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=NULL;		// Курсор при наведении на окно
	wndClass.hbrBackground=NULL;	// Закрашиваем окно белым цветом
	wndClass.lpszMenuName=NULL;	// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",		// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,	// Обычное окно с кнопками в правом верхнем углу.
		0, 0,				// Координаты X и Y
		GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
		GetSystemMetrics(SM_CYSCREEN),	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные

	if(hWnd==NULL) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	// Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		// Есть сообщение! Обработаем его как обычно...
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
		else
		{
			// Нет сообщений, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
	}

	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}

// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

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

Пример простейшего оконного приложения

Прежде чем начать разбираться в коде из Листинга 3, создадим Проект базового оконного приложения и скомпилируем его.

Создаём Проект WinTest01

  • Стартуй MS Visual C++ 2010, если не сделал этого раньше (подойдут и другие версии этой IDE).

О том, где взять и как правильно установить MS Visual C++ 2010 Express, читай здесь: MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

  • Создай новый Проект, выбрав в Главном меню IDE Файл->Создать->Проект.
  • В окне «Создать проект» выбираем: «Проект Win32», в строке «Имя» пишем название Проекта (в нашем случае WinTest01).

Строки «Расположение» и «Имя Решения» заполняются автоматически (при необходимости изменяем).

  • Жмём «OK», «Далее».

Image

  • На странице «Параметры приложения»: оставляем «Приложение Windows» и отмечаем пункт «Пустой проект».

Image

  • Жмём «Готово».

Проект создан. Так как это «Пустой проект», он не содержит в себе никаких файлов. В левой части главного окна MS Visual C++ 2010 расположен «Обозреватель решений». (Если его нет, в главном меню выбираем: Вид->Другие окна->Обозреватель решений. Или комбинация горячих клавиш Ctrl+Alt+L.) В Обозревателе решений видна древовидная структура Проектов, входящих в данное Решение. Чуть ниже названия Проекта видим специально заготовленные папки (в MSVC++2010 они называются «фильтры») для файлов Проекта (см Таблицу 1).

  • В фильтр «Файлы исходного кода» добавляем файл WinMain.cpp, точно так же, как это описано выше для консольного приложения.

Добавленный файл WinMain.cpp сразу же открывается в редакторе кода.

  • Вводим весь исходный код Листинга 3, показанного выше в только что созданный WinMain.cpp.

Можно просто всё скопировать и вставить.

  • Сохрани Решение (Файл -> Сохранить все).

Готовим Проект WinTest01 к компиляции

Для успешной компиляции Проекта WinTest01 изменим пару его свойств.

Устанавливаем многобайтовую кодировку (multibyte encoding)

Закрыть

noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 — напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, данный Проект мы настроим под многобайтовую кодировку.

Для этого…

  • В Обозревателе решений щёлкаем по названию только что созданного Проекта WinTest01.
  • Во всплывающем меню выбираем «Свойства»
  • В появившемся окне установки свойств Проекта жмём «Свойства конфигурации», в правой части в строке «Набор символов» выставляем значение «Использовать многобайтовую кодировку».

Image

  • Жмём ОК.

Отключаем инкрементную компоновку (incremental linking)
Инкрементная компоновка призвана сократить время компилирования. Но на деле её присутствие часто вызывает ошибки вроде этой:

Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден

Закрыть

warningОшибка LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

В MS Visual C++ 2010 даже компиляция консольных приложений нередко завершается неудачей, а вместо исполняемого файла программист видит сообщение:
LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt.

ПРИЧИНА: MS Visual С++ 2010 «не нравится» версия .NET Framework(external link), установленная в операционной системе. В общих чертах, MS Visual С++ 2010 спрограммирована для работы под управлением .NET Framework 4.0 и сильно к нему привязана. Если точнее, к нему сильно привязана система инкрементной компоновки приложений (incremental linking), которая по умолчанию включена для всех создаваемых проектов.
Во время установки MS Visual C++ 2010 пытается установить свой «родной» .NET Framework 4.0, проверяя версию этой программной платформы, установленную в ОС на данный момент. Если версия .NET Framework ниже 4.0, то она обновляется до 4.0 и всё прекрасно компилируется. Если версия .NET Framework выше 4.0, то всё оставляется как есть: IDE успешно завершает установку, но при компиляции ВСЕХ приложений выскакивает данная ошибка. Более того, ошибка была замечена даже при наличии в системе .NET Framework версии 4.0, но отличающейся от «родной» припиской вроде «Beta» или «Release Candidate».

ВАРИАНТЫ РЕШЕНИЙ:
1. Отключить инкрементную линковку в опциях Проекта.
В главном меню MSVC++2010 выбираем: Проект->Свойства->Свойства конфигурации->Компоновщик(Linker)->Включить инкрементное построение (Incremental Linking). Данный пункт по умолчанию включен для всех новых проектов. Выставляем его в Нет (No). И жмём OK. Инкрементное построение заметно сокращает время компилирования больших проектов. Но в нашем случае его отсутствие некритично.
2. Удалить (переместить в другое место) утилиту cvtres.exe из каталога bin установленной MS Visual C++ 2010.
В нашем случае (Win7 x64) полный путь до данного файла такой: C:Program Files (x86)Microsoft Visual Studio 10.0VCbincvtres.exe . Официальное название данного приложения — Microsoft® Resource File To COFF Object Conversion Utility (утилита конвертации файлов двоичных ресурсов в Component Object File Format). Опытным путём установлено, что при линковке IDE «спотыкается» именно об него.
3. Удалить из системы все версии .NET Framework (включая языковые пакеты и всякие профайлеры, если есть) и саму MS Visual C++ 2010.
Всё вышеперечисленное можно без труда найти в меню «Программы и компоненты» (MS Windows Vista/7/8). Затем заново установить MS Visual C++ 2010. При этом автоматом установится .NET Framework 4.0, идущий с ней в наборе.

Третий пункт — самый долгий. На деле почти всегда хватает выполнения первых двух. Данным вопросом озадачивались ребята здесь: https://www.cyberforum.ru/cpp-beginners/thread637174.html?ysclid=l3akpmanrq(external link). После этого линковка (=компоновка) проходит идеально. Подобные «костыли» в IDE от Майков — не редкость. Можно предположить, что команда с головой ударилась в тестирование .NET-возможностей MSVC++2010, совсем забыв о Win32-направлении (либо признав его бесперспективным).

Отключим инкрементную компоновку в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:

  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь.

В Обозревателе решений видим: «Решение «…»», а строкой ниже жирным шрифтом название Проекта. Обычно с тем же названием (если не менял вручную).

  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта.
  • Во всплывающем контекстном меню выбираем «Свойства».
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке «Включить инкрементную компоновку» ставим значение Нет (/INCREMENTAL:NO).
  • Жмём ОК.

Удаляем файл cvtres.exe
В нашем случае (Win7 x64) полный путь до данного файла такой: C:Program Files (x86)Microsoft Visual Studio 10.0VCbincvtres.exe
В Win32-кодинге он особо ни на что не влияет. Зато без него линковка идёт как по маслу.

Компилируем проект WinTest01

  • Скомпилируй Проект/Решение (кнопка с зелёным треугольником на Панели инструментов MSVC++ или <F5> на клавиатуре).

Если весь код был введён без ошибок, после компиляции запустится приложение, отображающее окно с белым фоном фоном.
Забегая вперёд, скажем, что белы фон прописан в коде строкой wndClass.hbrBackground=NULL; (цвет заливки NULL в Windows — белый).
Окно приложения развёрнуто на весь экран. За это в WinMain.cpp отвечают следующие строки:

Фрагмент WinMain.cpp

...
GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
GetSystemMetrics(SM_CYSCREEN),	// Высота окна
...

Закрыть

noteОбрати внимание

Если попытаться запустить скомпилированный .exe-файл на компьютере без установленной MSVC++2010, то экзешник (скорее всего) «ругнётся» на отсутствующую библиотеку MSVCR100D.dll. Буква D в её имени означает Debug, т.е. данное приложение скомпилировано с отладочной конфигурацией (профилем), которая выставляется по умолчанию у каждого вновь создаваемого Проекта/Решения. При релизе игры приложение напротив, компилируют с конфигурацией Release (релиз). При этом при создании инсталлятора в дистрибутив с игрой добавляют т.н. набор библиотек времени выполнения (в нашем слчае MS Visual C++ 2010 Runtime Redistributable). Он устанавливается вместе с игрой, т.к. без него игра тупо не стартанёт. Если для Release-профиля такой набор нетрудно нагуглить (например здесь: https://www.mydigitallife.net/visual-c-2010-runtime-redistributable-package-x86-x64-ia64-free-download(external link), 4,8 Мб для 64-разрядной версии ОС), то для запуска Debug-версии на отдельном компе на него потребуется установить целую MSVC++2010. Всё так сложно в том числе с целью не допустить утечек предрелизных разработок с компов игрокодерских компаний. Релиз есть релиз. А с дебаг-версиями, как правило, работают только сами игрокодеры, на своих компах отлавливая ошибки. Конфигурации Debug и Release легко переключаются на странице свойств открытого Проекта (в MSVC++2010 в главном меню выбираем Проект->Свойства, в верхней части диалога видим всплывающий список «Конфигурация»).

Скомпилированный исполняемый файл WinTest01.exe в нашем случае (Win7 x64) расположен по следующему пути: C:UsersUser1DocumentsVisual Studio 2010ProjectsWinTest01Debug . Его размер не превышает 30 Кб.

Исследуем код WinMain.cpp

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

  • WinMain — аналог функции Main, обязательной для каждого консольного приложения. Является точкой входа в программу.
  • WindowProc — называется оконной процедурой(external link) (название WindowProc, в принципе, выбрано произвольно, но желательно чтобы оно было «говорящим»). Обеспечивает реальную функциональность приложения.

На эту тему неплохо написано здесь: http://gamesmaker.ru/programming/c/vvedenie-v-winapi-chast-pervaya-sozdanie-okna(external link) .
Остальные небольшие функции реализуют реакцию программы на различные сообщения.
Весь процесс создания оконного Windows-приложения состоит из нескольких шагов:

  1. Регистрируем класс окна в ОС MS Windows. Для этого объявляем структуру типа WNDCLASSEX, заполняем должным образом её поля, после чего вызываем функцию RegisterClassEx.
  2. Создаём на базе зарегистрированного класса окна конкретный экземпляр окна (в нашем случае «GameClass») при помощи функции CreateWindowEx.
  3. Выводим окно на экран при помощи функции ShowWindow. При необходимости вызываем оконную процедуру для обновления рабочей области функцией UpdateWindow.
  4. Запускаем цикл выборки сообщений.

Но обо всём по порядку.
В начале Листинга 3 первое, что мы делаем — это указываем пару макроопределений и подключаем заголовочные файлы:

Фргамент WinMain.cpp

...
#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
...

Определения (define — от англ. «определить») специальных макросов перед включением файла windows.h служат для:

  • STRICT обеспечивает более строгую проверку типов. Например, при определённом STRICT компилятор выдаст сообщение об ошибке при присваивании объекта типа HBRUSH объекту типа HCURSOR. Если же STRICT не определён, то никакой ошибки компилятор не увидит. Таким образом можно обезопасить себя от некоторых распространённых ошибок, связанных с присваиванием объекту одного типа значения объекта другого типа (обычно вследствие невнимательности).
  • WIN32_LEAN_AND_MEAN уменьшает количество используемых компонентов и тем самым сокращает время на компиляцию и конечный размер исполняемого файла.

Заголовочные файлы и директива include

Заголовочный файл windows.h является обязательным для любой Windows-программы. Для предыдущих версий ОС MS Windows он содержал огромное количество объявлений типов, прототипов функций и т.д. Сейчас он включает в себя лишь ссылки на другие заголовочные файлы (их достаточно много). Налицо децентрализация и разбиение исходного кода на несколько независимых файлов-модулей.
В MSVC++ есть расширенная версия «заголовка» windows.h — windowsx.h. В нём также содержится множество полезных макросов и, в частности, распаковщики сообщений. Например он нужен для смены цвета фона окна средствами WinAPI. Но в нашем случае в соответствующей строке стоит NULL:

Фргамент WinMain.cpp

...
wndClass.hbrBackground=NULL; // Закрашиваем окно белым цветом.
...

Т.к. «рисовать» в окне будет DirectX, в нашем случае директива подключения windowsx.h в WinMain.cpp отсутствует. Оба этих «заголовка» расположены в одном из подкаталогов установленной MS Visual C++ (в принципе любой версии). Так, например, если открыть windows.h с помощью блокнота или любого другого текстового редактора, в нём можно обнаружить ссылку на другой заголовочный файл windef.h. В нём определены практически все специальные типы Windows, многие из которых встречаются в Листинге 3. Там также видим определения:

Фргамент windef.h

...
typedef	unsigned long	DWORD;
typedef	int	BOOL;
typedef	int	INT;
typedef	unsigned int	UINT;
...

С другими, специфичными для Windows типами, вроде HWND, HCURSOR, HBRUSH и др., всё сложнее.

Функция WinMain

  • Вызывается операционной системой и является точкой входа в программу (Вообще она вызывается некоей стартовой функцией из стандартной библиотеки C/C++, но сейчас это неважно).
  • Представляет собой аналог функции main, которую используют при создании консольных приложений.
  • Возвращает целое значение. В случае наличия цикла выборки сообщений g это должно быть поле структуры msg.wParam. Если его нет, то возвращается 0 (или не 0).
  • Её прототип (шаблон с указанием типов аргументов) выглядит так:

Прототип WinMain.cpp

...
int WINAPI WinMain(
 hINSTANCE hInst, // Идентификатор экземпляра окна (Instance handle)
 HINSTANCE hPrevInst, // Идентификатор экземпляра предыдущего окна (Previous Instance handle).
                     //Было актуально в Win 3.11 . Сейчас не используется.
 LPSTR lpzCmdLine, // Командная строка с опциями (если есть).
 int nCmdShow // Флаг показа окна.
);
...

Спецификатор WINAPI в заголовке WinMain указывает на то, что используется способ вызова, принятый в Win32 API. Способы вызова отличаются, например, порядком передачи аргументов (их соответствие принятым в ОС соглашениям очень важно).
Таблица 3. Параметры функции WinMain

Параметр Значение
HINSTANCE hInst Дескриптор текущего экземпляра приложения. Имеет тип HINSTANCE.
HINSTANCE hPrevInst Дескриптор предыдущего экземпляра приложения (если есть). Имеет тип HINSTANCE. В приложениях Win32 всегда равен NULL.
LPSTR lpzCmdLine Содержит командную строку запущенного приложения (исключая название этого приложения), со списком дополнительных аргументов (если есть). Имеет тип LPSTR (в конце всегда должен стоять символ «0»).
int nCmdShow Указывает на способ отображения главного окна программы.

Дескриптор экземпляра представляет собой уникальное значение и служит своеобразным идентификатором для поиска данной программы среди других запущенных приложений. Всякий раз при запуске оконного приложения Windows автоматически присваивает ему идентификатор экземпляра (instance handler), с помощью которого можно обращатся к процессу, созданному данным приложением. Самое ценное, что даёт идентификатор экземпляра — это возможность запускать несколько копий одного и того же приложения одновременно. При этом к ним надо как то обращаться. Обращение к ним как раз и проиходит через идентификатор экземпляра.
Windows-приложение может получать опции командной строки (command-line options; прямо как DOS-приложения). Оно получает их в виде указателя на строку (string pointer) lpCmdLine, которая парсится. В то же время параметры в Windows-приложениях довольно редки.
Последний параметр int nCmdShow может принимать следующие значения:
Таблица 4

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

На данном этапе мы заполнили данные, контролирующие выполнение приложения. После этого идёт процесс создания окна.

Заполняем структуру оконного класса

В ОС Windows при создании любого окна необходимо указать так называемый класс окна — специальную структуру, хранящуюся внутри ОС и задающую основные характеристики поведения всех окон своего класса. В то же время отличительные особенности каждого конкретного окна будут определены позднее, в момент его создания.
Сразу после объявления структуры (WNDCLASSEX wndClass;), начинаем заполнять все 12 её полей.

Закрыть

noteОбрати внимание

Структура данных WNDCLASSEX является немного расширенной версией своей предшественницы — WNDCLASS. В WNDCLASSEX появилось всего 2 новых элемента: cbSize (для указания размера структуры; здесь почти всегда стоит вызов функции sizeof(WNDCLASSEX)) и hIconSm (для указания малого значка приложения).

В Листинге 3 структура данных (data structure) WNDCLASSEX характеризует класс приложения, на базе которого будет создано окно программы:

Фргамент WinMain.cpp

...
// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения

	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX

   // Заполняем структуру оконного класса
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_CLASSDC;	// Поочерёдный доступ к контексту устройства (Device Context)
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=NULL;		// Курсор при наведении на окно
	wndClass.hbrBackground=NULL;	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
...

Напомним, что тип данных UINT — это Unsigned Integer (беззнаковое целое). Это означает, что числа данного типа не могут быть отрицательными.
Рассмотрим описание полей структуры WNDCLASSEX:
Таблица 5

Поле структуры Описание
cbSize Беззнаковое целое, в котором содержится размер структуры в байтах. В нашем случае в значении стоит служебная функция sizeof, самостоятельно подсчитывающая размер всей структуры wndClass.
style Беззнаковое целое, указывающее на стиль класса окна. Несколько стилей могут объединяться логической операцией ИЛИ (символ прямой вертикакльной черты «|»). Два самых распространённых стиля — CS_HREDRAW и CS_VREDRAW указывают на необходимость перерисовки окна при изменении его ширины и высоты. Стиль CS_DBLCLKS позволяет окну, созданному на базе этого класса, обрабатывать двойные щелчки мыши. Полный список значений смотри в Таблице 6.
lpfnWndProc Указатель на оконную процедуру (в нашем случае она называется WindowProc). Окна, созданные на базе одного класса, используют одну и ту же оконную процедуру. Именно поэтому прототип оконной процедуры был объявлен ПЕРЕД функцией WinMain.
cbClsExtra, cbWndExtra Дополнительные параметры оконного класса и окна соответственно. В теории здесь указывается число байт, дополнительно («экстренно») выделяемых оконному классу и его окну соответственно. На практике данные флаги, в общем-то, нигде в программировании не используются. И в нашем случае оба параметра выставляем в 0.
hInstance Дескриптор окна приложения, внутри которого располагается оконная процедура для оконного класса. Значение берётся из первого параметра функции WinMain.
hIcon, hIconSm Дескрипторы значков класса окна (большого и малого). Малый значок отображается в заголовке окна и на кнопке в панели задач. Для загрузки ресурсов в обоих случаях используется функция LoadIcon, где первый параметр — дескриптор приложения, второй — имя ресурса, содержащего значок (иконку). В нашем случае дескриптор равен NULL. Значит в этом случае будут использоваться стандартные значки. Возможные значения второго праметра для стандартных ресурсов: IDI_APPLICATION, IDI_ASTERISK, IDI_ERROR, IDI_EXCLAMATION, IDI_HAND, IDI_INFORMATION, IDI_QUESTION, IDI_WARNING, IDI_WINLOGO.
hCursor Дескриптор курсора (указателя) мыши класса окна. Для загрузки соответствующего ресурса используется функция LoadCursor с параметрами, аналогичными LoadIcon. Если первый параметр равен NULL, то в качестве второго параметра могут использоваться стандартные ресурсы указателей мыши: IDC_ARROW, IDC_UPARROW, IDC_CROSS, IDC_IBEAM, IDC_APPSTARTING, IDC_WAIT, IDC_HELP, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE.
hbrBackground Дескриптор кисти для заливки фона окна. В нашем случае стоит NONE, соответствующее белому цвету заливки. Как вариант, можно выставить значение GetStockBrush(BLACK_BRUSH), которое заливает клиентскую область окна чёрным цветом. Но для этого придётся подключить заголовок windowsx.h директивой #include.
lpszMenuName Указатель на строку с именем ресурса меню (lpsz означает, что эта строка всегда должна оканчиваться нулём). В нашем случае он равен NULL, то есть окно будет без меню.
lpszClassName Указатель на имя вновь создаваемого класса окна (lpsz означает, что эта строка всегда должна оканчиваться нулём). В нашем случае GameClass. Его мы передадим в качестве второго параметра функции CreateWindowEx.

Таблица 6. Возможные значения параметра style структуры WNDCLASSEX3

Значение Описание
CS_BYTEALIGNCLIENT Выравнивает клиентскую область окна (window’s client area; content area) на основании байтовой границы (byte boundary) по оси X. Это влияет на положение окна по горизонтали и на его ширину (width). Используется редко.
CS_BYTEALIGNWINDOW Выравнивает окно на основании байтовой границы (byte boundary) по оси X. Это влияет на положение окна по горизонтали и на его ширину (width). Используется редко.
CS_CLASSDC Говорит Windows выделять ресурсы прорисовки всем окнам данного класса. Используется при создании DirectX-приложений. Данный стиль выделяет (allocates) единый контекст устройства (device context), который будет применяться во всех окнах, созданных на основе данной структуры оконного класса. В общих чертах, в ОС Windows можно создавать несколько оконных классов одного типа в разных потоках (threads). В то время, как в ОС могут одновременно существовать несколько копий оконного класса, возможна ситуация, когда несколько потоков попытаются одновременно получить доступ к общему контексту устройства. При использовании данного стиля, доступ к контексту устройства в данный момент времени получает только 1 поток, в то время как остальные блокируются, ожидая своей очереди.
CS_DBLCLKS Здесь всё просто. При указании данного стиля, всякий раз, когда пользователь сделает двойной щелчок мышью в области окна, ОС Windows будет посылать сообщение о двойном щелчке мышью (double click). Сперва это может показаться ненужным, но многие приложения замеряют время каждого щелчка мышью в области окна для определения двойных щелчков, вместо того, чтобы просто использовать данный стиль.
CS_GLOBALCLASS Данный стиль разрешает создание глобального класса окна. В игрокодинге не используется. Более подробная информация по данному стилю есть в MSDN.
CS_HREDRAW Заставляет перерисовывать клиентскую область окна всякий раз при изменении его ширины. В программировании DirectX-приложений не применяется.
CS_VREDRAW Заставляет перерисовывать клиентскую область окна всякий раз при изменении его высоты. В программировании DirectX-приложений не применяется.
CS_NOCLOSE Скрывает кнопку с крестиком в верхнем правом углу тулбара окна.
CS_OWNDC Выделяет отдельный уникальный контекст устройства для каждого окна, создаваемого на основе данного оконного класса.
CS_PARENTDC При указании данного стиля дочернее окно (child window) имеет общую область отсечения (clipping area) с родительским окном (parent window). Это позволяет дочернему окну рисовать в родительском окне. Но это не значит, что дочернее окно использует контекст устройства родительского окна. Вместо этого дочернее окно получает свой собственный контекст устройства из системного пула. Данный флаг применяется в основном для увеличения быстродействия приложения.
CS_SAVEBITS Сохраняет в памяти картинку (bitmap) области, расположенной под окном. Данный флаг препятствует отправке сообщения WM_PAINT любым окнам, расположенным под данным окном.

В некоторых источниках по DirectX-программированию структуру оконного класса не расписывают так подробно, выставив «декоративные» параметры в NULL:

// Структура оконного класса DirectX-приложения
WNDCLASSEX wcex = (sizeof(WNDCLASSEX), CS_CLASSDC, WindowProc, 0L, 0L, hInstance,
                   NULL, NULL, NULL, NULL, "GameClass", NULL);

При создании окна обычного (не DirectX) приложения структура оконного класса будет выглядеть по-другому:

// Структура оконного класса обычного (не DirectX) приложения.
WNDCLASSEX wcex = (sizeof(WNDCLASSEX), CS_HREDRAW|CS_VREDRAW, WindowProc, 0L, 0L, hInstance,
                   LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW),
                   (HBRUSH)GetStockObject(BLACK_BRUSH), NULL, "AppClass", NULL);

Закрыть

noteЛюбопытно

Регистрация оконного класса

После заполнения полей структуры WNDCLASSEX происходит её регистрация путём вызова функции RegisterClassEx, где в качестве параметра стоит указатель на имя уже заполненной структуры (wndClass):

Фргамент WinMain.cpp

...
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;
...

Сама функция «обрамлена» условным переходом if, которая возвращает FALSE в случае неудачи и досрочно завершает работу программы.

Закрыть

noteПроверка с условным переходом if

Подобные проверки являются отличным средством и помогают обезопасить себя от множества «непонятных» вылетов, аварийного завершения приложения, а главное — от утечек памяти (например, в случае неудачной регистрации структуры оконного класса). Опытные программисты ставят такие проверки везде, где только можно.

С момента регистрации новый оконный класс становится известным операционной системе, и теперь на его базе можно создавать окна.

Создание окна

Создание окна осуществляется с помощью функции CreateWindow. Вот её прототип:

Прототип функции CreateWindow

HWND CreateWindow(
 LPCSTR lpClassName,	// Класс окна (в нашем случае GameClass)
 LPCSTR lpWindowName,	// Имя окна. В принципе, любое. Пишется в заголовке окна
 DWORD dwStyle,	// Основной стиль окна
 int x, int y,	// Координаты расположения окна
 int nWidth, int nHeight,	// Ширина и высота
 HWND hWndParent,	// Дескриптор родительского окна (если есть)
 HMENU hMenu,	// Дескриптор меню (если есть)
 HINSTANCE hInstance,	// Дескриптор экземпляра окна
 LPVOID lpParam	// Дополнительные параметры
);

Сравним этот прототип с реальной функцией CreateWindow из Листинга 3:

Фргамент WinMain.cpp

...
	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindow(
		"GameClass",		// Класс окна.
		"My game title",			// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,		// Обычное окно с кнопками в правом верхнем углу.
		0, 0,					// Координаты X и Y
		GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
		GetSystemMetrics(SM_CYSCREEN),	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные
...

Закрыть

noteЛюбопытно

У функции CreateWindow есть более расширенный «собрат» — CreateWindowEx, в который добавлен один (!) новый параметр dwExStyle, т.н. расширенный стиль окна, который может принимать более 20 различных значений (их описание нетрудно найти в Интернете и MSDN). В игрокодинге под Win32 расширенные (улучшенные) стили окна не применяются. Поэтому наш выбор — функция CreateWindow. В свете того, что CreateWindowEx появилась ещё аж в Windows 3.0 (а CreateWindow — ещё раньше), можно представить, на сколь древнем «монолите» будет базироваться код нашего оконного приложения. Дискуссию о преимуществах «нового» доп. параметра читаем здесь: https://devblogs.microsoft.com/oldnewthing/20201207-00/?p=104518(external link) .

В комментариях всё подробно объясняется. Но отметим несколько моментов:

  • Третий параметр DWORD dwStyle (в нашем случае он имеет значение WS_OVERLAPPEDWINDOW) указывает на основной стиль окна.

Закрыть

noteПримечание

При создании DirectX-приложений всегда ставят значение WS_OVERLAPPEDWINDOW, позволяющее и пользователю, и DirectX свободно изменять размеры окна по своему усмотрению.

Значений может быть несколько. В этом случае их разделяют знаком логического ИЛИ (символ прямой черты «|»). Так вот, этот параметр может принимать следующие значения:
Таблица 7. Возможные значения параметра dwStyle (основной стиль окна)

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

Поэкспериментируй с исходным кодом из Листинга 3, подставляя различные значения этого параметра и отправляя после этого код на перекомпиляцию. В нашем случае здесь стоит значение WS_OVERLAPPEDWINDOW.

  • В четвёртом и пятом параметрах (int x, int y) в качестве координаты левого верхнего угла указывается точка (0,0). Это необходимо для того, чтобы окно занимало весь экран.
  • В шестом и седьмом параметрах (int nWidth, int nHeight) указывается ширина и высота окна. В нашем случае эти размеры совпадают с шириной и высотой экрана (т.к. у нас окно развёрнуто на весь экран). Узнаём ширину и высоту экрана с помощью вспомогательной функции int GetSystemMetrics(int nIndex), получающей в качестве параметра специальные макросы SM_CXSCREEN (для ширины) или SM_CYSCREEN (для высоты), указывающие, что нужно узнать ширину и высоту в точках экрана первичного монитора (Primary display).

В результате успешного выполнения функция CreateWindow возвращает дескриптор типа HWND. В случае неудачи возвращается NULL.
Именно поэтому, сразу после описания функции CreateWindow, идёт проверка с условным оператором if:

Фргамент WinMain.cpp

...
	if(hWnd==NULL) return(FALSE);
...

Если по окончании выполнения дескриптор приложения равен NULL, то досрочно завершаем выполнение программы.
Окно создано, но на экране оно не появится до тех пор, пока не будет вызвана функция ShowWindow (HWND hWnd, int nCmdShow):

Фргамент WinMain.cpp

...
	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
...

В первом параметре она получает дескриптор вновь созданного окна (hWnd), а во втором — одну из констант с префиксом SW_ из Таблицы 4, указывающую на способ отображения окна. Но чаще всего здесь просто указывают параметр nCmdShow. Функция UpdateWindow заставляет окно обновить свою клиентскую область всякий раз, когда происходит перемещение окна или изменяются его размеры.

Конвейер (цикл) выборки сообщений (Message pump)

  • Является «сердцем» любой Windows-программы.
  • Может строиться по-разному (часто на основе всевозможных ветвящихся алгоритмов).
  • Конструируется (объявляется) внутри функции WinMain.
  • В нашей программе выглядит так:

Фргамент WinMain.cpp

...
	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	// Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		// Есть сообщение! Обработаем его как обычно...
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
		else
		{
			// Нет сообщений, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
	}
...

Здесь наше приложение входит в бесконечный цикл, ожидая сообщения от ОС Windows. Как только они поступают, то через функции GetMessage или PeekMessage переправляются в оконную процедуру для обработки. В самом «сердце» цикла выборки сообщений расположена функция PeekMessage(). Вот её прототип:

Прототип функции PeekMessage()

BOOL PeekMessage(
 LPMSG lpMsg,	// Указатель на структуру MSG
 HWND hWnd,	// Указатель на окно (кому сообщение)
 UINT wMsgFilterMin,	// для маски минимум
 UINT wMsgFilterMax,	// для маски максимум
 INT wRemoveMsg	// Что делать с сообщением
);

Последний параметр здесь может принимать следующие значения:

Значение Описание
PM_REMOVE Удалять сообщение из очереди.
PM_NOREMOVE Не удалять сообщение из очереди.

Функция PeekMessage вернёт ненулевое значение, если в очереди есть сообщения. Если фильтра нет, то будут получены все сообщения, при наличии фильтра только по фильтру.
Также вместо неё можно применить аналогичную по действию функцию GetMessage().

Закрыть

noteРазница между функциями PeekMessage и GetMessage

Функция GetMessage() является блокирующей. То есть она ожидает пока не будет получено сообщение из очереди. До этого момента программа стоит. Что делать, если надо просто проверить есть ли сообщение в очереди? (Например, чтобы занять программу в этот момент другими действиями, например, упаковкой базы данных.) Для этого и есть функция PeekMessage(). Она проверяет есть ли в очереди сообщение и программа выполняется дальше.

Обрати внимание, что GetMessage() вернет WM_QUIT даже при наличии фильтра (оператора switch…case). MS Windows является событийно-ориентированной операционной системой. Она устроена так, что на протяжении всего времени «жизни» запущенного приложения оно постоянно «бомбардируюется» всевозможными сообщениями, информирующими обо всех изменениях, происходящих в рабочем окружении ОС (сдвинулся курсор мыши, запустили другое приложение и т.д.). Эти сообщения выбираются из очереди сообщений g (Message queue), которую ОС организует для каждой программы, а затем передаются в оконную процедуру (в нашем случае это функция WindowProc) для обработки. БОльшая часть из этих сообщений абсолютно «не интересна» нашему приложению и они передаются обратно Windows для так называемой обработки по умолчанию.
Чтобы сообщение было получено программой, в наличии должны иметься 2 компонента:

  • Точка назначения (участок кода, которому собственно и передаются сообщения). В нашем случае это оконная процедура WindowProc). Именно она получает в качестве аргумента сообщение, адресованное окну, с которым она связана (+ параметры этого сообщения).
  • Механизм передачи (программа должна сама организовать выборку сообщений из всей очереди). Может быть синхронным и асинхронным:

Таблица 9. Виды механизмов (способов) передачи сообщений

Синхронный Реализуется в случае вызова оконной процедуры непосредственно операционной системой Windows. Яркий пример — функция UpdateWindow, вызывающей оконную процедуру, и передавая ей в качестве параметра сообщение WM_PAINT (оно требует прорисовать рабочую область окна). Другой способ послать оконной процедуре синхронное сообщение — воспользоваться функцией SendMessage (см. её прототип сразу под этой таблицей). Она посылает указанное сообщение в окно, определяемое дескриптором hWnd, и НЕ возвращает управление до тех пор, пока не произойдёт возврат из оконной процедуры. Синхронность в данном случае подразумевает возможность предсказать, в какой момент времени (точнее, после какого оператора) произойдёт обработка сообщения.
Асинхронный Реализуется при помощи очереди сообщений, организуемой для каждого приложения (точнее, для каждого потока приложения, который может принимать пользовательский ввод). ОС MS Windows помещает в эту очередь все сообщения. Лишь небольшая их часть может относиться к работающей программе. Приложение выбирает из этой очереди адресованные ему сообщения и обрабатывает их по мере поступления.

Прототип функции SendMessage

LRESULT SendMessage(
 HWND hWnd,	// Дескриптор окна UINT Msg, // Номер сообщения
 WPARAM wParam,	// Первый параметр
 WPARAM lParam	// Второй параметр
);

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

Прототип функции PostMessage

LRESULT PostMessage(
 HWND hWnd,	// Дескриптор окна UINT Msg, // Номер сообщения
 WPARAM wParam,	// Первый параметр
 WPARAM lParam	// Второй параметр
);

Разница между SendMessage и PostMessage состоит в следующем:

  • Функция SendMessage непосредственно вызывает оконную процедуру, передавая ей нужное сообщение;
  • Функция PostMessage помещает это сообщение в очередь, связанную с создавшим окно программным потоком (thread), и, не дожидаясь обработки сообщения, возвращает управление вызывающей функции. А уже само приложение затем извлечёт сообщение из очереди и должным образом обработает. Именно такую схему называют циклом выборки сообщений.

В нашем случае в этом цикле стоит функция PeekMessage. В конце этого цикла стоят две функции : BOOL TranslateMessage(CONST MSG *lpMsg) LONG DispatchMessage(CONST MSG *lpMsg) .
Обе они получают в качестве параметра адрес структуры типа MSG, поля которой заполнены предварительным вызовом функции PeekMessage.

  • TranslateMessage обычно «транслирует» сообщения, связанные с пользовательским вводом (нажатие клавиш, перемещение мыши) в символьные данные (character data).

Как только сообщение было транслировано в символьные данные, его можно «запостить» в очередь сообщений (message queue):

  • DispatchMessage вызывает оконную процедуру, передавая ей информацию о сообщении, полученную при помощи функции PeekMessage. Функция передаёт (помещает) оттранслированные сообщения в очередь сообщений оконной процедуры приложения.

Сам цикл выборки сообщений «обрамлён» циклом while do else, где в условии стоит ненаступление события WM_QUIT, прерывающего выполнение цикла и завершающего работу приложения, что и делает оператор return (msg.wParam).

Оконная процедура и обработка сообщений

Из-за используемой Windows схемы «Не зови меня, я сама тебе позову» нам необходимо оснастить наше приложение процедурой выборки сообщений (window message procedure более известная как оконная процедура), которая будет принимать входящий поток сообщений. Если функция WinMain представляет своего рода «мотор» программы, то оконная процедура наделяет приложение настоящей функциональностью. Именно в ней обрабатываются сообщения, переданные с помощью функции DispatchMessage. Большинство приложений отличаются друг от друга главным образом реакцией на сообщения, которые им посылает ОС MS Windows. Вот прототип оконной процедуры (название произвольное; в нашем случае — WindowProc):

Прототип функции WindowProc

LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

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

Фргамент WinMain.cpp

...
// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}
...

Внутри неё видим единственный оператор switch. А всё потому, что всё, что делает данная функция, это «просматривает» различные типы сообщений, которые в неё указаны, и проверяет, не совпадает ли какое-либо из этих сообщений с передаваемым. В нашем случае мы проверяем всего 1 сообщение на совпадение — WM_DESTROY. Получив данное сообщение, наше приложение вызывает внутреннюю функцию Windows PostQuitMessage, закрывающую его окно. То же самое происходит и при нажатии кнопки закрытия в правом верхнем углу окна приложения (в виде крестика). При этом код сообщения передаётся в оконную процедуру в качестве параметра uMsg. Обычная (созданная нами) оконная процедура обрабатывает отдельные (указанные в ней) сообщения. А те, что не обрабатывает сама, передаёт в оконную процедуру по умолчанию, которая имеет прототип:

Прототип функции DefWindowProc

LRESULT DefWindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Как видим, оконная процедура по умолчанию имеет тот же тип возвращаемого результата и такой же список параметров, что и обычная оконная процедура. Различие состоит в том, что код «умолчательной» оконной процедуры встроен в саму ОС MS Windows, и она умеет обрабатывать, в принципе, все сообщения. Основная задача функции DefWindowProc — выполнять действия по умолчанию (часто это означает не делать ничего). Тем не менее, согласно правилам Windows-программирования, любое необработанное явным образом (с помощью обычной оконной процедуры) сообщение необходимо передавать в оконную процедуру по умолчанию (функция DefWindowProc).
В начале Листинга 3 при заполнении структуры класса приложения мы произвели присваивание:

Фргамент WinMain.cpp

...
wndClass.lpfnWndProc=WindowProc;	// Оконная процедура
...

Благодаря этому, MS Windows знает оконную процедуру класса и пересылает в неё все адресуемые окну сообщения. Каждое сообщение сопровождается двумя параметрами с типами WPARAM, LPARAM. Они содержат упакованные в них характеристики сообщений. В Win32 оба эти параметра имеют размер 32 бита. Встроенные в ОС MS Windows распаковщики сообщений избавляют нас от необходимости работать с данными параметрами напрямую.

Код завершения работы приложения

При закрытии окна дерегистрируем (unregister) оконный класс и выходим из приложения:

Фргамент WinMain.cpp

...
	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}
...

Пишем текст в окне

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

Изменения в WinMain.cpp

В оконной процедуре стоит оператор switch..case, реагирующий на разный события.

  • В самом конце Main.cpp найди следующий фрагмент:

Фргамент WinMain.cpp

...
// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

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

  • Перед оператором switch добавь инициализирующие переменные, необходимые для отрисовки текста:
HDC hDC; // Контекст устройства
PAINTSTRUCT ps;
RECT rect;

  • Перед оператором case WM_DESTROY добавь второй оператор case и сопутствующий код:
case WM_PAINT: // При отрисовке окна...
 hDC = BeginPaint(hWnd, &ps);
 GetClientRect(hWnd, &rect);
 DrawText(hDC, TEXT("Этo тестовое приложение. Всем привет!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
 EndPaint(hWnd, &ps);
 return 0;

После внесённых изменений оконная процедура будет выглядеть так:

Фргамент WinMain.cpp

...
// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	HDC hDC; // Контекст устройства
	PAINTSTRUCT ps;
	RECT rect;

	switch(msg)
	{
	case WM_PAINT: // При отрисовке окна...
		hDC = BeginPaint(hWnd, &ps);
		GetClientRect(hWnd, &rect);
		DrawText(hDC, TEXT("Этo тестовое приложение. Всем привет!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
		EndPaint(hWnd, &ps);
		return 0;

	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}

  • Сохрани Решение/Проект (Файл -> Сохранить все).
  • Скомпилируй Проект/Решение (F5).

Если набрал код без ошибок, то после успешной компиляции на экране вновь покажется чёрное окно приложения с текстом, выравненным по центру. Смотри-ка, MSVC++ неплохо дружит с русскими кодировками даже при использовании многобайтовой кодировки (указанной в графе «Набор символов» в свойствах Проекта). А всё из-за специального макроса TEXT (см функцию DrawText), который выведет заданный текст одинаково даже на компах с азиатискими версиями ОС Windows.

Сообщения Windows (теория)

Image

Рис.1 Событие, став сообщением, проходит несколько этапов обработки

ОС Windows впервые представила концепцию, согласно которой операционная система сама информирует приложение, когда происходит что-то важное. Например, юзер переместил мышь (см. Рис.1). При этом автоматически создаётся событие (event), которое ОС Windows получает, обрабатывает и уже в виде сообщения (message) посылает приложению.
В ОС Windows есть глобальная очередь (global queue), в которой она сохраняет все поступившие сообщения. Она не спеша разбирает каждое из них и затем отправляет их запущенным приложениям. При получении сообщения приложение сохраняет его в своей собственной очереди сообщений (application message queue). Внутри каждой Windows-программы есть т.е. насос сообщений (message pump или message loop — цикл выборки сообщений), который последовательно сканирует каждое из них, выискивая подходящие сообщания для обработки. Как только такое появляется, цикл выборки сообщений приложения посылает его в специальную процедуру оконных сообщений (window message procedure), также расположенную в недрах программы. Эта функция обрабатывает единичные сообщения, большая часть которых пропускается или «отфутболивается» обратно в ОС Windows.

Закрыть

noteОбрати внимание

Все Windows-сообщения имеют специальный префикс WM_ в своём текстовом идентификаторе (windows message).

В ОС MS Windows имеется огромное количество сообщений, которыми она непрерывно «бомбардирует» оконную процедуру. Лишь малая часть из них (иногда всего 2-3) необходима программисту для обработки в каждом конкретном случае. Большинство Windows-сообщений определены в файле winuser.h, расположенном в каталоге с установленной MS VC++. Вот самые распространённые из них:
Таблица 10. Сообщения, связанные с управлением окнами

Сообщение Описание
WM_ACTIVATE Посылается в окно в случае его активации/деактивации.
WM_CREATE Посылается, когда совершается попытка создать окно при помощи функций CreateWindow или CreateWindowEx. Оконная процедура получает это сообщение ПОСЛЕ того, как окно создано, но ДО того, как оно появится на экране. В случае если оконная процедура в ответ на это сообщение возвращает значение -1, окно не создаётся и функция, использованная для создания окна, возвращает NULL.
WM_CLOSE Посылается в окно всякий раз, когда оно должно быть закрыто. Это сообщение может обрабатываться в случае, если приложению нужно выполнить определённые действия перед тем, как закрыться.
WM_DESTROY Посылается в удаляемое окно, когда оно уже убрано с экрана.
WM_MOVE Посылается в окно после того, как изменилось его положение на экране.
WM_SIZE Посылается в окно при изменении его размеров. Также посылается в окно всякий раз при его создании.
WM_QUIT Помещается в очередь сообщений после вызова функции PostQuitMessage и является командой для завершения приложения. Функция PeekMessage (иногда вместо неё применяют схожую функцию GetMessage), выбрав из очереди это сообщение, возвращает ноль. После этого цикл выборки сообщений прекращается, а вместе с ним завершается и работа программы.
WM_PAINT Посылается в окно при перерисовке всего его содержимого (либо участка этого окна). Является частью библиотеки GDI.

Таблица 11. Сообщения клавиатуры

Сообщение Описание
WM_KEYDOWN Посылается в очередь сообщений, когда окно имеет клавиатурный фокус и несистемная клавиша была отпущена (отжата). Передаёт в оконную процедуру виртуальный код клавиши. Список виртуальных кодов клавиш содержится в файле winuser.h .
WM_KEYUP Посылается в очередь сообщений, когда окно имеет клавиатурный фокус и была нажата какая-либо несистемная клавиша. Передаёт в оконную процедуру виртуальный код клавиши.
WM_CHAR Помещается в очередь сообщений как результат выполнения функции TranslateMessage после распаковки сообщения о нажатой несистемной клавише WM_KEYDOWN.
WM_MOUSEMOVE Помещается в очередь сообщений, при перемещении указателя (курсора) мыши над окном.
WM_LBUTTONDOWN Помещается в очередь сообщений, когда нажимается левая кнопка мыши и курсор в это время находится над клиентской областью окна.
WM_LBUTTONUP Помещается в очередь сообщений, если левая кнопка мыши отпущена (отжата) и курсор в это время находится над клиентской областью окна.
WM_LBUTTONDBLCLK Помещается в очередь сообщений, когда произведён двойной щелчок левой кнопкой мыши и курсор в это время находится над клиентской областью окна.
WM_RBUTTONDOWN Помещается в очередь сообщений, когда нажимается правая кнопка мыши и курсор в это время находится над клиентской областью окна.
WM_RBUTTONUP Помещается в очередь сообщений, если правая кнопка мыши отпущена (отжата) и курсор в это время находится над клиентской областью окна.
WM_RBUTTONDBLCLK Помещается в очередь сообщений, когда произведён двойной щелчок правой кнопкой мыши и курсор в это время находится над клиентской областью окна.
WM_MBUTTONDOWN Помещается в очередь сообщений, когда нажимается средняя кнопка мыши и курсор в это время находится над клиентской областью окна.
WM_MBUTTONUP Помещается в очередь сообщений, если средняя кнопка мыши отпущена (отжата) и курсор в это время находится над клиентской областью окна.
WM_MBUTTONDBLCLK Помещается в очередь сообщений, когда произведён двойной щелчок средней кнопкой мыши и курсор в это время находится над клиентской областью окна.
WM_SETCURSOR Посылается в окно, если указатель мыши перемещается над окном и при этом мышь не была захвачена никаким другим окном. Часто используется для задания вида указателя мыши.

Таблица 12. Другие сообщения

Сообщение Описание
WM_TIMER Помещается в очередь событий по истечении срока, указанного при создании таймера.
WM_COMMAND Посылается, когда был выбран какоц-либо пункт меню, обработан акселератор или когда дочерний элемент управления отправляет сообщение родительскому окну.

Если программер задумал обработать определённое сообщение Windows, ему необходимо выяснить тип параметров сообщения и другую доп. информацию о нём. Доп. информация (extra information) содержится в аргументах lParam и wParam сообщения. Подробная информация обо всех сообщениях Windows и их аргументах как всегда содержится в MSDN и в недрах Windows SDK.
В качестве примера разберём сообщение WM_MOUSEMOVE. Согласно документации Windows SDK, аргумент lParam является 32-битным значением. Нижние 16 бит (low-word) аргумента lParam содержат X-координату курсора мыши, а верхние 16 бит (hi-word) — Y-координату. Для извлечения значений low-word и hi-word используют макросы LOWORD и HIWORD соответственно:

int XCoordinate = LOWORD(lParam);
int YCoordinate = HIWORD(lParam);

Распаковщики сообщений

  • Представляют собой специальные макросы, определённые в файле windowsx.h.
  • Позволяют оформлять обработку каждого оконного сообщения (например в оконной процедуре) в виде отдельной функции.
  • Уменьшают объём исходного кода, улучшают его читабельность.
  • Повышают модульность программы, так как обработчик каждого сообщения может располагаться в отдельной функции, что способствует созданию более структурированных, а следовательно, более надёжных программ.
  • Позволяют писать хорошо структурированный код, что повышает его понимаемость и сопровождаемость.
  • При их использовании программист избавлен от необходимости заниматься явным приведением типов при обработке сообщений (этот процесс нередко приводит к ошибкам).

Рассмотрим пример оконной процедуры без применения распаковщиков сообщений:

...
LRESULT WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_CREATE:
		if(!SetTimer(hWnd,TIMER_ID, TIMER_RATE, NULL))
			return (-1L);
		return (0L);
	case WM_TIMER:
		MessageBeep(0xFFFFFFFF);
		return (0L);
	case WM_DESTROY:
		KillTimer(hWnd, TIMER_ID);
		PostQuitMessage(0);
		Return (0L);

	default:
	return DefWindowProc(hWnd, msg, wParam, lParam);
	}
}
...

Как видим, даже при обработке 3-4 сообщений листинг выглядит громоздко. Более того:

  • Каждое сообщение несёт в себе дополнительную информацию в параметрах wParam и lParam (в данной процедуре они не используются, но вообще применяются довольно часто).
  • Для каждого сообщения дополнительная информация пакуется в wParam и lParam совершенно различными способами (их описание можно найти в документации).
  • Способ упаковки зависит и от версии Windows: Win16 и Win32 делают это для некоторых сообщений по-разному.

Для решения этих проблем и предназначены распаковщики сообщений (message crackers). При применении распаковщиков сообщений фрагмент исходного кода фрагмент

...
	case WM_CREATE:
		if(!SetTimer(hWnd,TIMER_ID, TIMER_RATE, NULL))
			return (-1L);
		return (0L);
...

можно переписать как

...
	case WM_CREATE:
		return HANDLE_WM_CREATE(hWnd, wParam, lParam, FirstApp_OnCreate);
...

Здесь распаковщик преобразует IParam к типу LPCREATESTRUCT и передаёт переменную этого типа в функцию FirstApp_OnCreate (заранее подготовленную программистом). Тот же оператор можно записать ещё проще:

...
HANDLE_MSG(hWnd, WM_CREATE, FirstApp_OnCreate);
...

Так как распаковщики являются всего лишь макросами, они удаляются из текста программы ещё на этапе препроцессорной обработки. Таким образом они никак не сказываются на эффективности программы.

Дополнительные возможности программирования Windows

Создав два приложения (консольное и оконное), мы затронули лишь верхушку айсберга Windows-программирования. Представленный ниже материал в принципе факультативен (=второстепенен). Но любой уважающий себя Windows-программер должен это знать. Кроме того, с помощью стандартных диалоговых окон часто создаются конфигураторы (= окна настроек видео и звука игры). Про многопоточность — вообще молчу. Если сумеешь повесить на отдельный процесс подзагрузку локации, не прерывая игровой процесс, цены тебе не будет как игрокодеру.

Всплывающее (модальное) окно сообщения (Message Box)

Донести информацию до пользователя часто бывает совсем не просто. Текст сообщения должен быть в отдельном окне. А создание окна, как мы выяснили, целая военно-морская операция. Для упрощения вывода сообщений были придуманы всплывающие окна. Всплывающее окно сообщения (Message Box) представляет собой специальное (сильно упрощённое) окно, у которого есть строка заголовка (caption), само сообщение, значок (иконка; опционально) и до трёх кнопок. Будучи выведенным на экран, окно сообщения просто ждёт от пользователя клика по своей кнопке. Как только это произойдёт, окно сообщения закрывается и передаёт выполнение исполняемому коду приложения, которое его вызвало.
Создать окно сообщения совсем не трудно. Вот его прототип:

Прототип окна MessageBox

int MessageBox(
	HWND hWnd, // Родительское окно. Или NULL, если его нет.
	LPCSTR lpText, // Выводимое сообщение.
	LPCSTR lpCaption // Текст заголовка окна.
	UINT uType // Настройки кнопок и значка окна сообщения.
	);

Параметр uType представляет собой комбинацию флагов, определяющих, какие кнопку и значок показать в окне сообщения. Флаги могут объединяться логическим ИЛИ (|). Вот далеко не полный список флагов:
Таблица 13. Возможные значения параметра uType окна MessageBox

Значение Описание
MB_OK Показывает кнопку OK.
MB_OKCANCEL Показывает кнопки OK и Cancel (отмена).
MB_RETRYCANCEL Показывает кнопки OK и Retry (повторить).
MB_YESNO Показывает кнопки YES и NO.
MB_YESNOCANCEL Показывает кнопки YES, NO и Cancel (отмена).
MB_ABORTRETRYFAIL Показывает кнопки ABORT (отмена), RETRY (повторить) и FAIL (отмена).
MB_ICONEXCLAMATION Показывает значок с восклицательным знаком.
MB_ICONCONFIRMATION Показывает значок с маленькой i.
MB_ICONQUESTION Показывает значок со знаком вопроса.
MB_IConstop Показывает значок со знаком STOP.
MB_DEFBUTTON1 Первая кнопка жмётся по умолчанию при нажатии Enter.
MB_DEFBUTTON2 Вторая кнопка жмётся по умолчанию при нажатии Enter.
MB_DEFBUTTON3 Третья кнопка жмётся по умолчанию при нажатии Enter.

Полный список, как всегда, ищем в MSDN.
После нажатия пользователем одной из кнопок окно сообщения возвращает одно из следующих значений:
Таблица 14. Значения, возвращаемые окном сообщения

Значение Описание
IDABORT Нажата кнопка ABORT.
IDCANCEL Нажата кнопка CANCEL.
IDIGNORE Нажата кнопка IGNORE.
IDYES Нажата кнопка YES (Да).
IDNO Нажата кнопка NO (Нет).
IDOK Нажата кнопка OK.
IDRETRY Нажата кнопка RETRY (повторить).

Вот пример окна сообщения, предлагающего пользователю нажать одну из двух кнопок YES или NO. Например при запросе о выходе из программы:

int Result = MessageBox( NULL, "Выйти из программы без сохранения изменений?",
        "Выход из программы", MB_YESNO | MB_ICONQUESTION);

Для пущей убедительности окно сообщения оформляется значком с вопросительным знаком. При нажатии YES (Да) возвращается значение IDYES. При нажатии NO (Нет) возвращается IDNO.
Часто окна сообщений применяются в качестве точек останова (breakpoints) при отладке программы. Например, такое сообщение можно вставить после выполнения какой-нибудь функции, проверив её успешное выполнение:

MessageBox(hWnd, "Объект устройства Direct3D создан!", "GameInit", MB_OK)

Если при запуске приложения это окно сообщения не появилось, то проверяемая функция не выполнилась.

Диалоговые окна (Dialog Boxes)

  • Являются ещё одной продвинутой фичей Windows.
  • Представляют собой оконные приложения, созданные с использованием специальных шаблонов (template), разработанных с помощью редактора ресурсов.

Закрыть

noteОбрати внимание

В бесплатной MSVC++2010 Express редактор ресурсов вырезан напрочь. Поэтому для создания специального скрипта ресурсов воспользуемся сторонним приложением ResEdit, которое нетрудно найти в Интернете или скачать здесь(external link). На igrocoder.ru есть хорошая статья по этой проге: Работа с двоичными ресурсами в MSVCpp2010 Express.

У диалоговых окон есть куча преимуществ:

  • Удобное размещение элементов управления на форме (почти как в Delphi).
  • На одном шаблоне можно быстро создать одно или несколько диалоговых окон.
  • Диалоговые окна, подобно обычным окнам, используют для обработки поступающих сообщений процедуру выборки сообщений (window message procedure).

Но есть и отличия. Например, при создании диалоговое окно получает сообщение WM_INITDIALOG вместо WM_CREATE. Рассмотрим прототип диалогового окна:

HWND CreateDialog(
	HINSTANCE hInstance, // Дескриптор экземпляра приложения (Handle to application instance).
	LPCSTR lpTemplate, // Указатель на шаблон (template) диалогового окна.
	HWND hWndParent, // Дескриптор родительского окна.
	DLGPROC lpDialogFunc // Процедура выборки сообщений диалогового окна.
	);

Файл с двоичными ресурсами готовится заранее и содержит шаблон диалога (обычно это форма с нанесёнными на неё элементами управления). Каждый шаблон имеет уникальный идентификатор. Для создания окна из шаблона диалогового окна мы передаём в функцию CreateDialog в качестве параметров:

  • уникальный идентификатор,
  • дескриптор экземпляра окна (application’s instance handle),
  • процедуру выборки сообщений (message procedure).

По выполнении функция CreateDialog возвращает дескриптор (handle) созданного диалогового окна. Рассмотрим подробнее процесс создания двоичных ресурсов.

Двоичные ресурсы Windows (Binary resources)

(Двоичные) ресурсы представляют собой данные, размещаемые в «хвосте» исполняемого файла. К стандартным ресурсам Windows относятся:

  • Диалоговые окна (Dialog)
  • Значки (Icons)
  • Определения меню (Menu)
  • Определения панелей инструментов (Toolbar)
  • Растровые изображения (Bitmap)
  • Курсоры мыши (Cursor)

Кроме стандартных, программист может сам определить свои собственные ресурсы. Игрокодеру достаточно знать лишь сам принцип добавления различных двоичных ресурсов в приложение.

Потоки и мультипоточность (Threads and Multithreading).

Windows 95 впервые представила программерам идею выполнения на одном компе несколько задач одновременно (т.е. многозадачность). Вообще, Windows 95 на самом деле не была многозадачной, т.к. в ней использовалась т.н. вытесняющая (preemptive) многозадачность. Это когда ОС выполняет несколько программ поочерёдно и малыми порциями. Суть такой многозадачности в том, что несколько приложений (или процессов) выполняются в одно и то же время, но каждое из них поочерёдно получает доступ к (одному) процессору на малый промежуток времени, называемый временной срез (time slice). Всё это происходит настолько быстро, что создаётся иллюзия того, что несколько программ выполняются одновременно на одном компе.
Многозадачность также позволяет разбивать каждый процесс на несколько других процессов, называемых потоками (threads). Каждый поток имеет своё назначение. Например, один сканирует данные сети, другой обрабатывает пользовательский ввод и при необходимости воспроизводит звуки и т.д.. Использование в приложении более одного потока называется многопоточностью (multithreading). В общих чертах поток представляет собой функцию, выполняющуюся параллельно с основным приложением. В игрокодинге потоки обычно создают при работе со звуком и сетью.
Создать дополнительные потоки в приложении совсем нетрудно. Для этого сначала создаётся специальная т.н. функция потока (thread function) (с использованием её специального прототипа), содержащая выполняемый код. Вот её прототип:

DWORD WINAPI ThreadProc(LPVOID lpParameter);

Здесь параметр lpParameter — это определённый пользователем указатель (user-defined pointer), который указывается при создании потока.
Сам поток создаётся функцией CreateThread:

HANDLE CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAbilities, // NULL
	DWORD dwStackSize, // 0
	LPTHREAD_START_ROUTINE lpStartAddress, // Функция потока
	LPVOID lpParameter, // Указатель, предоставляемый пользователем. Может быть NULL
	DWORD dwCreationFlags, // 0
	LPDWORD lpThreadId // Получает идентификатор потока (thread identifier)
	);

Закрыть

noteОбрати внимание

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

Представленный выше код создаёт поток, который начинает свою работу сразу после завершения выполения функции CreateThread.

Закрыть

noteОбрати внимание

ExitThread() — единственная функция, останавливающая работу потока. Вызывается из самого потока. Для завершения потока из внешнего кода применяют функцию TerminateThread(). Но применять TerminateThread нежелательно, т.к. она чрезмерно расходует ресурсы и может привести к аварийному завершению программы. Функция TerminateThread запрашивает 2 аргумента: дескриптор потока (thread handle) и код завершения (termination code; обычно 0).

Во время вызова функции CreateThread в параметре мы передаём указатель на переменную типа BOOL, которая отслеживает состояние потока (значение TRUE означает, что поток активен; FALSE — неактивен). По завершении выполнения потока данный флаг принимает значение FALSE. Затем поток уничтожается путём вызова функции ExitThread(), единственный параметр которой — код завершения, или по-другому «причина», по которой поток завершает свою работу. Обычно здесь всегда ставят 0.

Критические секции (Critical sections)

В свете того, что Windows — многозадачная ОС, приложения в ней могут конфликтовать друг с другом. Особенно если это приложения с несколькими потоками. Представь ситуацию, когда один поток заполняет структуру данных оч. важными данными, когда в это же время второй поток пытается получить доступ или изменить эти данные.
Есть способ убедиться в том, что только 1 поток (или процесс) в настоящее время имеет доступ к объекту данных. Для этого были придуманы критические секции (critical sections). Во время применения критической секции, фрагмент выполняемого кода становится недоступен для всех других процессов, которые пытаются получить к нему доступ (доступ к памяти приложения, которую по умолчанию одновременно используют все его потоки). Это позволяет отдельному потоку индивидуально изменять данные приложения, не заботясь о том, что другие потоки помешают этому. Для использования критической секции её надо сначала объявить, а затем инициализировать:

CRITICAL_SECTION CriticalSection;
InitializeCriticalSection(&CriticalSection);

Теперь можно войти в критическую секцию, обработать важные данные и затем покинуть критическую секцию:

EnterCriticalSection(&CriticalSection);
// Выполняем всякие операции над важными данными
LeaveCriticalSection(&CriticalSection);

По завершении работы с критической секцией (например при закрытии приложения) мы освобождаем занятые ею ресурсы:

DeleteCriticalSection(&CriticalSection);

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

Итоги

Windows API — интересная тема, тесно связанная с Windows GDI. Программы, написанные на «голом» WinAPI, будут работать в любой версии ОС Windows, начиная с Win95. В игрокодинге под Win32 на WinAPI пишется базовое оконное приложение, являющееся отправной точкой для создания будущей игры.

Источники


Понравилась статья? Поделить с друзьями:
  • Как переустановить касперского после переустановки windows
  • Как писать программы в блокноте windows
  • Как повернуть монитор на 90 градусов горячие клавиши windows 10
  • Как переустановить калькулятор в windows 10
  • Как писать приложения под ios на windows