Программирование под Windows в среде Visual C++ 2005

Программирование под Windows с использованием Win API

Разбить на страницы
Показывать лекцию целиком

Основные понятия и термины, используемые при разработке Windows приложений

Примеры программ для лекции.

Ядро Windows:

  • USER (16, 32) .dll – функции ввода с клавиатуры мыши, ввод через интерфейс и т.д. (взаимодействие приложений с пользователями и средой Windows).
  • KERNEL (16, 32) .dll – функции операционной системы (память, распределение системных ресурсов, загрузка).
  • GDI (16, 32) .dll – графический интерфейс (функции создания и отображения графических объектов).
  • GUI (Graphics User Interface) – стандартный графический интерфейс пользователя. Это та часть Windows, которая обеспечивает поддержку аппаратно-независимой графики.

    API (Application Program Interface) — интерфейс прикладных программ (набор функций, сосредоточенных в ядре Windows и дополнительных библиотеках).

    DLL (Dynamic Link Libraries) — библиотека динамической компоновки. Функции API содержатся в библиотеках динамической загрузки.

    DDE – динамический обмен данными.

    Нотация Windows ("венгерская нотация Чарльза Симони")

    При программировании под Windows принято использовать префиксы перед именами переменных, указывающие на принадлежность к типу данных. Рекомендуется давать имена собственным переменным и идентификаторам, придерживаясь следующих принципов:

  • мнемоническое значение – идентификатор должен легко запоминаться;
  • смысловое значение – роль идентификатора должна быть ясна из его названия;
  • преемственность – похожие объекты должны иметь похожие идентификаторы;
  • быстрота принятия решения – придумывание, ввод и редактирование идентификатора не должны занимать много времени.
  • Некоторые префиксы венгерской нотации:

    Префикс Значение
    A массив
    B логический тип (int)
    By беззнаковый символьный тип (byte)
    C символьный тип (1 байт)
    Cb счетчик байтов
    Cr цвет
    cx,cy короткий тип (short)
    Dbl double (с плавающей точкой)
    Dw беззнаковое длинное целое число (dword)
    Flt float (вещественная с плавающей точкой)
    Fn функция
    g_ префикс для глобальной переменной (глобальная переменная)
    H handle (беззнаковое целое число)
    hDC handle (указатель на контекст устройства)
    I целое (integer)
    Id интегральное значение идентификатора
    L длинный тип (long)
    Lp длинный указатель
    Lpsz дальний указатель на строку, заканчивающуюся нулевым байтом
    m_ переменная класса
    N short или int
    Np ближний указатель
    P указатель
    Pfn указатель на функцию
    Pst указатель на структуру
    Psz указатель на строку, заканчивающуюся нулевым байтом
    Pv указатель на тип void
    S строка
    Sz строка, заканчивающая нуль-символом
    U беззнаковый символ
    Tm текстовая метрика
    V тип void
    W беззнаковое целое (word, 16-бит)
    x, y короткое целое число (координата x или y)

    Часто используемые типы данных Windows:

    Тип данных Описание
    HANDLE определяет идентификатор; 32-разрядное целое, используемое в качестве дескриптора – числа, определяющего некоторый ресурс
    HWND определяет идентификатор окна
    HDC определяет идентификатор контекста устройства
    LONG 32-битовое целое со знаком
    LPSTR определяет линейный указатель
    NULL 0
    UINT тип данных Win32 (32 бита для Win32)
    WCHAR 16-битовый символ UNICODE. Используется для представления символов языков мира

    Создание простейшего Windows-приложения с использованием Win API

    Элементы Windows-приложения

    Построение приложения Windows включает выполнение следующих этапов:

  • Создание WinMain(...) и связанных с ней функций на языке C или C++.
  • Создание описаний меню и всех дополнительных ресурсов, помещение описаний в файл описания ресурсов.
  • Создание уникальных курсоров, пиктограмм и битовых образов.
  • Создание диалоговых окон.
  • Создание файла проекта.
  • Компиляция и компоновка всего кода.
  • Простейшая программа. Создание и вывод Windows-окна на экран

    Создадим пустой проект Windows- приложения с помощью мастера:

  • File $$\to$$ New $$\to$$ Project.
  • Project types: Win32 Templates: Win32 Project.
  • Ok.
  • Установить галочку Empty project.
  • Добавить в проект файл *.cpp.
  • Project $$\to$$ Properties. Вкладка Configuration Properties $$\to$$ General.
  • Значение поля Character Set устанавливаем Use Multi-Byte Character Set.
  • Добавим следующий код:

    #include <windows.h>
    LONG WINAPI WndProc(HWND, UINT, WPARAM,LPARAM);
    int  WINAPI  WinMain (HINSTANCE  hInstance,
                         HINSTANCE  hPrevInstance,
                         LPSTR    lpCmdLine,
                          int    nCmdShow)
    {
      HWND hwnd;
      MSG msg;
      WNDCLASS w;
      memset(w,0,sizeof(WNDCLASS));
      w.style = CS_HREDRAW | CS_VREDRAW;
      w.lpfnWndProc = WndProc;
      w.hInstance = hInstance;
      w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      w.lpszClassName = "My Class";
      RegisterClass(w);
      hwnd = CreateWindow("My Class", "Окно пользователя", WS_OVERLAPPEDWINDOW,500, 300, 500, 380, NULL, NULL, hInstance, NULL);
      ShowWindow(hwnd,nCmdShow);
      UpdateWindow(hwnd);
      while(GetMessage(msg,NULL,0,0))
      {
        TranslateMessage(msg);
        DispatchMessage(msg);
      }
      return msg.wParam;
    }
    
    LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
    {
      switch (Message)
      {
        case WM_DESTROY: PostQuitMessage(0); break;
        default: return DefWindowProc(hwnd, Message, wparam, lparam);
      }
      return 0;
    }

    Скомпилируем и запустим программу. На экране появится Windows-окно.

    Комментарии к программе

    Все приложения Windows должны содержать два основных элемента: функцию WinMain(...) и функцию окна WndProc.

    Функция WinMain(...) служит точкой входа в приложение. Эта функция отвечает за следующие действия:

  • регистрацию типа класса окон приложения;
  • выполнение всех инициализирующих действий;
  • создание и инициализацию цикла сообщений приложения;
  • завершение программы (обычно при получении сообщения WM_QUIT ).
  • Функция WndProc отвечает за обработку сообщений Windows. Эта часть программы является наиболее содержательной с точки зрения выполнения поставленных перед программой задач. Если мы хотим, чтобы программа обращала на наши действия внимание, то необходимо добавить ветки case для оператора switch в оконную процедуру WndProc. Например, если мы хотим, чтобы наше приложение обращало внимание на щелчок левой кнопкой мыши – добавляем ветку case WM_LBUTTONDOWN. В настоящий момент в оконной процедуре происходит только обработка сообщения WM_DESTROY. Больше Windows-окно пока ничего делать не умеет.

    Заголовочный файл windows.h нужен для любой традиционной Windows программы на C. Именно в нем содержатся разные определения констант ( WM_DESTROY и т. д.).

    Параметры функции WinMain:

  • hInstance (тип HINSTANCE ) – является идентификатором текущего экземпляра приложения. Данное число однозначно определяет программу, работающую под управлением Windows.
  • hPrevInstance (тип HINSTANCE ) – указывал ранее (Windows 3.1) на предыдущий запущенный экземпляр приложения. В современных версиях Windows он равен NULL.
  • lpCmdLine – это указатель на строку, заканчивающуюся нулевым байтом. В этой строке содержатся аргументы командной строки приложения (как правило, содержит NULL ).
  • nCmdShow – этот параметр принимает значение одной из системных констант, определяющих способ изображения окна (например, SW_SHOWNORMAL, SW_SHOWMAXIMIZED или SW_SHOWMINIMIZED ).
  • Регистрация класса окна

    Каждое окно, которое создается в рамках приложения Windows, должно основываться на классе окна – шаблоне, в котором определены выбранные пользователем стили, шрифты, заголовки и т.д. Для всех определений класса окна используется стандартный тип структуры. Таким образом, сначала определяется структура WNDCLASS w, а затем поля структуры заполняются информацией о классе окна. У этой структуры много полей, но большинство из них можно определить нулем. В программе это делается строкой memset(w,0,sizeof(WNDCLASS)) ;

    Рассмотрим следующий фрагмент программы:

    w.style = CS_HREDRAW | CS_VREDRAW;
      w.lpfnWndProc = WndProc;
      w.hInstance = hInstance;
      w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      w.lpszClassName = "My Class";
      RegisterClass(w);

    В этом фрагменте определяется стиль класса (все идентификаторы стилей начинаются с префикса CS_ ). В программе значения поля стиля задаются константами. CS_HREDRAW ¬– обеспечивает перерисовку содержимого клиентской области окна при изменении размера окна по горизонтали, CS_VREDRAW – обеспечивает перерисовку содержимого клиентской области окна при изменении размера окна по вертикали.

    w.lpfnWndProc = WndProc - значение указателя на функцию окна ( WndProc ), которая выполняет все задачи, связанные с окном.

    w.hInstance = hInstance - определяется экземпляр приложения, регистрирующий класс окна.

    w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH) - определяется кисть, используемая для закраски фона окна.

    w.lpszClassName = "My Class" - указатель на строку символов, заканчивающуюся на 0, которая определяет имя класса. Класс здесь – это не класс в смысле ООП (объектно-ориентированного программирования). Термин один, но смысла два. Исторически классы окон возникли раньше, чем классы ООП.

    RegisterClass(w) - регистрация происходит при помощи вызова функции RegisterClass().

    Создание окна на основе класса окна

    hwnd = CreateWindow("My Class","Окно пользователя", WS_OVERLAPPEDWINDOW,500,300,500,380,NULL,NULL,hInstance,NULL);

    Первый параметр функции служит для задания класса окна. Второй – это заголовок окна. Третий – определяет стиль окна (обычное перекрывающее окно с заголовком, кнопкой вызова системного меню, кнопками минимизации и максимизации и рамкой). Следующие шесть параметров определяют положение окна на экране (по оси X и по оси Y), размеры окна по оси X и по оси Y, идентификатор родительского окна и идентификатор меню окна. Следующее поле ( hInstance ) содержит идентификатор экземпляра программы, далее следует информация об отсутствии дополнительных параметров ( NULL ). Если создание окна прошло успешно, то функция CreateWindow(...) возвращает идентификатор созданного окна, в противном случае – NULL. После того как окно создано, его надо показать и обновить. Для того чтобы вывести главное окно приложения на экран, необходимо вызвать функцию Windows ShowWindow(...).

    ShowWindow(hwnd,nCmdShow) выводит окно на экран. Параметр hwnd содержит идентификатор окна, созданного при вызове CreateWindow(...). Второй параметр определяет, как окно выводится в первый момент.

    Последний шаг при выводе окна на экран заключается в вызове функции Windows UpdateWindow(hwnd) , которая приводит к перерисовке клиентской области окна.

    Создание цикла обработки сообщений

    Теперь программа готова выполнять свою главную задачу – обрабатывать сообщения. Используется стандартный цикл С/С++ цикл while:

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

    Использование функции GetMessage

    Вызов этой функции позволяет получить для обработки следующее сообщение из очереди сообщений приложения. Данная функция копирует сообщение в структуру сообщения, на которую указывает указатель msg, и передает его в основной блок программы. Если значение следующего параметра NULL, то будут поступать сообщения, относящиеся к любому окну приложения. Значения последних параметров (0,0) указывает функции, что не надо применять никаких фильтров сообщений. Фильтры могут применяться для распределения получаемых сообщений по категориям, например, нажатия на клавишу или перемещение мыши. После входа в цикл обработки сообщений, выйти из него можно, получив только одно сообщение: WM_QUIT. Когда обрабатывается сообщение WM_QUIT, то возвращается значение "ложь" и цикл обработки сообщений завершается.

    Использование функции TranslateMessage

    Функция TranslateMessage(...) преобразует сообщения виртуальных клавиш в сообщения о символах.

    Использование функции DispatchMessage

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

    Оконная функция WndProc

    Вторая часть в любой программе под Windows – это оконная процедура. В рассматриваемом примере она маленькая (так как программа ничего не делает), но, именно эта часть и является самой главной и интересной в приложении.

    Рассмотрим функцию WndProc:

    LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
    {
      switch (Message)
      {
        case WM_DESTROY: PostQuitMessage(0); break;
        default: return DefWindowProc(hwnd, Message, wparam, lparam);
      }
      return 0;
    }

    Основное назначение оконной функции – это обработка сообщений Windows. Каждое приложение получает много сообщений. Их источник может быть разным. Например, сообщения от пользователя или от самой Windows. Обработка этих сообщений происходит именно в оконной функции. Это означает, что для каждого сообщения необходимо написать свой обработчик. Если обработчика не будет, то приложение не будет обращать внимание на сообщение. У оконной функции четыре параметра. Первый из них hwnd типа HWND задает окно, которое будет обрабатывать сообщение. Второй UINT Message – это передаваемое сообщение. Два последних WPARAM wparam, LPARAM lparam задают дополнительные параметры для передаваемого сообщения. Они для каждого сообщения свои. Оконная процедура отправляет сообщение в switch, который в примере имеет только один case:

    switch (Message)
    {
      case WM_DESTROY:
      ...

    То есть, пока рассматриваемая программа обращает внимание только на сообщение WM_DESTROY. Это сообщение окно получает только при своем уничтожении. После принятия этого сообщения необходимо вызвать функцию PostQuitMessage(...). В ответ на сообщение WM_DESTROY необходимо поместить в очередь сообщение WM_QUIT. Это и делает функция PostQuitMessage(...), посылая это сообщение в очередь и говоря, что процесс должен быть завершен. Если мы хотим, чтобы наша программа реагировала еще на что-нибудь, то надо написать еще case.

    Рассмотрим далее ветку default. В ней идет вызов функции DefWindowProc(hwnd, Message, wparam, lparam) . Основное предназначение этой функции – обработка сообщений Windows, которые не обрабатываются в нашей программе (то есть, для которых нет своего case ). При этом ничего не делается, но очередь из сообщений идет.

    Пример разложения в ряд функции. Графический вывод

    Проиллюстируем рассмотренный в предыдущем параграфе теоретичекий материал на примере написания Windows-приложения решения задачи о разложении в ряд некоторой функции.

    Задача: Разложить в ряд Тейлора в окрестности точки 0 функции .

    Напишем функции вычисления синуса и косинуса – разложения в ряд Тейлора в окрестности точки 0. Разложения в ряд имеют вид:

    $$sin(x)=x-\frac{x^3}{3!}+...+(-1)^{n-1}\cdot\frac{x^{2n-1}}{(2n-1)!}}$$ $$cos(x)=1-\frac{x^2}{2}+...+(-1)^{n}\cdot\frac{x^{2n}}{(2n)!}}$$

    Радиус сходимости для этих рядов – $$\infty$$.

    const double epsilon = 0.0000001;
    const double pi = 3.14159265;
    const double x_start = -2*pi;
    const double x_end = 2*pi;
    
    double sin_e(double arg, double eps)
    {
      double result = 0, rn = arg;
      for(int i = 1; ;i++)
      {
        if(rn < eps  rn > -eps) return result;  
        result += rn;
        rn = -rn*arg*arg/(i+1)/(i+2);  
        i++;  
      }
    }
    
    double cos_e(double arg, double eps)
    {
      double result = 0, rn = 1;
      for(int i = 0; ;i++)
      {
        if(rn < eps  rn > -eps) return result;  
        result += rn;
        rn = -rn*arg*arg/(i+1)/(i+2);  
        i++;  
      }
    }

    Для дальнейшего решения задачи – вывода графика функции в клиентском окне, будем обрабатывать сообщения, которые будут посылаться окну. Когда необходима перерисовка окна (изменились размеры окна, часть окна перекрылась другим окном и т.п.), система посылает окну сообщение WM_PAINT.

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

    Для построения изображения используем API функции операционной системы: MoveToEx(...), LineTo(...), GetClientRect(...) и т.д. Информацию об этих функциях можно получить в справочной системе.

    void DrawAxis(HDC hdc, RECT rectClient)
    {
      HPEN penGraph = CreatePen(PS_SOLID,2,RGB(0,0,255));
      HGDIOBJ gdiOld = SelectObject(hdc, penGraph);
      MoveToEx(hdc, 0, rectClient.bottom/2, NULL);
      LineTo(hdc, rectClient.right, rectClient.bottom/2);
      LineTo(hdc, rectClient.right - 5, rectClient.bottom/2 + 2);
      MoveToEx(hdc, rectClient.right, rectClient.bottom/2, NULL);
      LineTo(hdc, rectClient.right - 5, rectClient.bottom/2 - 2);
      MoveToEx(hdc, rectClient.right/2, rectClient.bottom, NULL);
      LineTo(hdc, rectClient.right/2, rectClient.top);
      LineTo(hdc, rectClient.right/2 - 2, rectClient.top + 5);
      MoveToEx(hdc, rectClient.right/2, rectClient.top, NULL);
      LineTo(hdc, rectClient.right/2 + 2, rectClient.top + 5);
      SelectObject(hdc, gdiOld);
    }
    
    void DrawGraph(HDC hdc, RECT rectClient)
    {
      HPEN penGraph = CreatePen(PS_SOLID,2,RGB(255,0,0));
      HGDIOBJ gdiOld = SelectObject(hdc, penGraph);
      double x_current = x_start;
      double step = (x_end - x_start)/rectClient.right;
      double y_start = cos_e(x_start, epsilon) + cos_e(x_start, epsilon);
      MoveToEx(hdc, 0, int(-y_start/step) + rectClient.bottom/2, NULL);
      while(x_current < x_end)
      {  
        x_current += step;
        double y_next = cos_e(x_current, epsilon) + cos_e(x_current, epsilon);
        LineTo(hdc, int(x_current/step) + rectClient.right/2, int(-y_next/step) + rectClient.bottom/2);
      }
      SelectObject(hdc, gdiOld);
    }
    
    void OnPaint(HWND hwnd)
    {
      PAINTSTRUCT ps;
      RECT rectClient;
      HDC hdc = BeginPaint(hwnd,ps);
      GetClientRect(hwnd, rectClient);
      DrawAxis(hdc, rectClient);
      DrawGraph(hdc, rectClient);
      ValidateRect(hwnd,NULL);
      EndPaint(hwnd,ps);
    }
    
    LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
    {
      switch (Message)
      {
        case WM_PAINT:
          OnPaint(hwnd);
        break;
        case WM_MOUSEMOVE:
          SetCapture(hwnd);
          SetCursor(LoadCursor(NULL,IDC_ARROW));
          ReleaseCapture();
        break;
        case WM_WINDOWPOSCHANGED:
          UpdateWindow(hwnd);
        break;
        case WM_DESTROY: PostQuitMessage(0); break;
        default: return DefWindowProc(hwnd, Message, wparam, lparam);
      }
      UpdateWindow(hwnd);
      return 0;
    }

    Функция :рис 1.1

    (рис 1.1) Построение графика функции
    Страницы:

    Основные понятия и термины, используемые при разработке Windows приложений

    Примеры программ для лекции.

    Ядро Windows:

  • USER (16, 32) .dll – функции ввода с клавиатуры мыши, ввод через интерфейс и т.д. (взаимодействие приложений с пользователями и средой Windows).
  • KERNEL (16, 32) .dll – функции операционной системы (память, распределение системных ресурсов, загрузка).
  • GDI (16, 32) .dll – графический интерфейс (функции создания и отображения графических объектов).
  • GUI (Graphics User Interface) – стандартный графический интерфейс пользователя. Это та часть Windows, которая обеспечивает поддержку аппаратно-независимой графики.

    API (Application Program Interface) — интерфейс прикладных программ (набор функций, сосредоточенных в ядре Windows и дополнительных библиотеках).

    DLL (Dynamic Link Libraries) — библиотека динамической компоновки. Функции API содержатся в библиотеках динамической загрузки.

    DDE – динамический обмен данными.

    Нотация Windows ("венгерская нотация Чарльза Симони")

    При программировании под Windows принято использовать префиксы перед именами переменных, указывающие на принадлежность к типу данных. Рекомендуется давать имена собственным переменным и идентификаторам, придерживаясь следующих принципов:

  • мнемоническое значение – идентификатор должен легко запоминаться;
  • смысловое значение – роль идентификатора должна быть ясна из его названия;
  • преемственность – похожие объекты должны иметь похожие идентификаторы;
  • быстрота принятия решения – придумывание, ввод и редактирование идентификатора не должны занимать много времени.
  • Некоторые префиксы венгерской нотации:

    Префикс Значение
    A массив
    B логический тип (int)
    By беззнаковый символьный тип (byte)
    C символьный тип (1 байт)
    Cb счетчик байтов
    Cr цвет
    cx,cy короткий тип (short)
    Dbl double (с плавающей точкой)
    Dw беззнаковое длинное целое число (dword)
    Flt float (вещественная с плавающей точкой)
    Fn функция
    g_ префикс для глобальной переменной (глобальная переменная)
    H handle (беззнаковое целое число)
    hDC handle (указатель на контекст устройства)
    I целое (integer)
    Id интегральное значение идентификатора
    L длинный тип (long)
    Lp длинный указатель
    Lpsz дальний указатель на строку, заканчивающуюся нулевым байтом
    m_ переменная класса
    N short или int
    Np ближний указатель
    P указатель
    Pfn указатель на функцию
    Pst указатель на структуру
    Psz указатель на строку, заканчивающуюся нулевым байтом
    Pv указатель на тип void
    S строка
    Sz строка, заканчивающая нуль-символом
    U беззнаковый символ
    Tm текстовая метрика
    V тип void
    W беззнаковое целое (word, 16-бит)
    x, y короткое целое число (координата x или y)

    Часто используемые типы данных Windows:

    Тип данных Описание
    HANDLE определяет идентификатор; 32-разрядное целое, используемое в качестве дескриптора – числа, определяющего некоторый ресурс
    HWND определяет идентификатор окна
    HDC определяет идентификатор контекста устройства
    LONG 32-битовое целое со знаком
    LPSTR определяет линейный указатель
    NULL 0
    UINT тип данных Win32 (32 бита для Win32)
    WCHAR 16-битовый символ UNICODE. Используется для представления символов языков мира

    Создание простейшего Windows-приложения с использованием Win API

    Элементы Windows-приложения

    Построение приложения Windows включает выполнение следующих этапов:

  • Создание WinMain(...) и связанных с ней функций на языке C или C++.
  • Создание описаний меню и всех дополнительных ресурсов, помещение описаний в файл описания ресурсов.
  • Создание уникальных курсоров, пиктограмм и битовых образов.
  • Создание диалоговых окон.
  • Создание файла проекта.
  • Компиляция и компоновка всего кода.
  • Простейшая программа. Создание и вывод Windows-окна на экран

    Создадим пустой проект Windows- приложения с помощью мастера:

  • File $$\to$$ New $$\to$$ Project.
  • Project types: Win32 Templates: Win32 Project.
  • Ok.
  • Установить галочку Empty project.
  • Добавить в проект файл *.cpp.
  • Project $$\to$$ Properties. Вкладка Configuration Properties $$\to$$ General.
  • Значение поля Character Set устанавливаем Use Multi-Byte Character Set.
  • Добавим следующий код:

    #include <windows.h>
    LONG WINAPI WndProc(HWND, UINT, WPARAM,LPARAM);
    int  WINAPI  WinMain (HINSTANCE  hInstance,
                         HINSTANCE  hPrevInstance,
                         LPSTR    lpCmdLine,
                          int    nCmdShow)
    {
      HWND hwnd;
      MSG msg;
      WNDCLASS w;
      memset(w,0,sizeof(WNDCLASS));
      w.style = CS_HREDRAW | CS_VREDRAW;
      w.lpfnWndProc = WndProc;
      w.hInstance = hInstance;
      w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      w.lpszClassName = "My Class";
      RegisterClass(w);
      hwnd = CreateWindow("My Class", "Окно пользователя", WS_OVERLAPPEDWINDOW,500, 300, 500, 380, NULL, NULL, hInstance, NULL);
      ShowWindow(hwnd,nCmdShow);
      UpdateWindow(hwnd);
      while(GetMessage(msg,NULL,0,0))
      {
        TranslateMessage(msg);
        DispatchMessage(msg);
      }
      return msg.wParam;
    }
    
    LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
    {
      switch (Message)
      {
        case WM_DESTROY: PostQuitMessage(0); break;
        default: return DefWindowProc(hwnd, Message, wparam, lparam);
      }
      return 0;
    }

    Скомпилируем и запустим программу. На экране появится Windows-окно.

    Комментарии к программе

    Все приложения Windows должны содержать два основных элемента: функцию WinMain(...) и функцию окна WndProc.

    Функция WinMain(...) служит точкой входа в приложение. Эта функция отвечает за следующие действия:

  • регистрацию типа класса окон приложения;
  • выполнение всех инициализирующих действий;
  • создание и инициализацию цикла сообщений приложения;
  • завершение программы (обычно при получении сообщения WM_QUIT ).
  • Функция WndProc отвечает за обработку сообщений Windows. Эта часть программы является наиболее содержательной с точки зрения выполнения поставленных перед программой задач. Если мы хотим, чтобы программа обращала на наши действия внимание, то необходимо добавить ветки case для оператора switch в оконную процедуру WndProc. Например, если мы хотим, чтобы наше приложение обращало внимание на щелчок левой кнопкой мыши – добавляем ветку case WM_LBUTTONDOWN. В настоящий момент в оконной процедуре происходит только обработка сообщения WM_DESTROY. Больше Windows-окно пока ничего делать не умеет.

    Заголовочный файл windows.h нужен для любой традиционной Windows программы на C. Именно в нем содержатся разные определения констант ( WM_DESTROY и т. д.).

    Параметры функции WinMain:

  • hInstance (тип HINSTANCE ) – является идентификатором текущего экземпляра приложения. Данное число однозначно определяет программу, работающую под управлением Windows.
  • hPrevInstance (тип HINSTANCE ) – указывал ранее (Windows 3.1) на предыдущий запущенный экземпляр приложения. В современных версиях Windows он равен NULL.
  • lpCmdLine – это указатель на строку, заканчивающуюся нулевым байтом. В этой строке содержатся аргументы командной строки приложения (как правило, содержит NULL ).
  • nCmdShow – этот параметр принимает значение одной из системных констант, определяющих способ изображения окна (например, SW_SHOWNORMAL, SW_SHOWMAXIMIZED или SW_SHOWMINIMIZED ).
  • Регистрация класса окна

    Каждое окно, которое создается в рамках приложения Windows, должно основываться на классе окна – шаблоне, в котором определены выбранные пользователем стили, шрифты, заголовки и т.д. Для всех определений класса окна используется стандартный тип структуры. Таким образом, сначала определяется структура WNDCLASS w, а затем поля структуры заполняются информацией о классе окна. У этой структуры много полей, но большинство из них можно определить нулем. В программе это делается строкой memset(w,0,sizeof(WNDCLASS)) ;

    Рассмотрим следующий фрагмент программы:

    w.style = CS_HREDRAW | CS_VREDRAW;
      w.lpfnWndProc = WndProc;
      w.hInstance = hInstance;
      w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      w.lpszClassName = "My Class";
      RegisterClass(w);

    В этом фрагменте определяется стиль класса (все идентификаторы стилей начинаются с префикса CS_ ). В программе значения поля стиля задаются константами. CS_HREDRAW ¬– обеспечивает перерисовку содержимого клиентской области окна при изменении размера окна по горизонтали, CS_VREDRAW – обеспечивает перерисовку содержимого клиентской области окна при изменении размера окна по вертикали.

    w.lpfnWndProc = WndProc - значение указателя на функцию окна ( WndProc ), которая выполняет все задачи, связанные с окном.

    w.hInstance = hInstance - определяется экземпляр приложения, регистрирующий класс окна.

    w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH) - определяется кисть, используемая для закраски фона окна.

    w.lpszClassName = "My Class" - указатель на строку символов, заканчивающуюся на 0, которая определяет имя класса. Класс здесь – это не класс в смысле ООП (объектно-ориентированного программирования). Термин один, но смысла два. Исторически классы окон возникли раньше, чем классы ООП.

    RegisterClass(w) - регистрация происходит при помощи вызова функции RegisterClass().

    Создание окна на основе класса окна

    hwnd = CreateWindow("My Class","Окно пользователя", WS_OVERLAPPEDWINDOW,500,300,500,380,NULL,NULL,hInstance,NULL);

    Первый параметр функции служит для задания класса окна. Второй – это заголовок окна. Третий – определяет стиль окна (обычное перекрывающее окно с заголовком, кнопкой вызова системного меню, кнопками минимизации и максимизации и рамкой). Следующие шесть параметров определяют положение окна на экране (по оси X и по оси Y), размеры окна по оси X и по оси Y, идентификатор родительского окна и идентификатор меню окна. Следующее поле ( hInstance ) содержит идентификатор экземпляра программы, далее следует информация об отсутствии дополнительных параметров ( NULL ). Если создание окна прошло успешно, то функция CreateWindow(...) возвращает идентификатор созданного окна, в противном случае – NULL. После того как окно создано, его надо показать и обновить. Для того чтобы вывести главное окно приложения на экран, необходимо вызвать функцию Windows ShowWindow(...).

    ShowWindow(hwnd,nCmdShow) выводит окно на экран. Параметр hwnd содержит идентификатор окна, созданного при вызове CreateWindow(...). Второй параметр определяет, как окно выводится в первый момент.

    Последний шаг при выводе окна на экран заключается в вызове функции Windows UpdateWindow(hwnd) , которая приводит к перерисовке клиентской области окна.

    Создание цикла обработки сообщений

    Теперь программа готова выполнять свою главную задачу – обрабатывать сообщения. Используется стандартный цикл С/С++ цикл while:

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

    Использование функции GetMessage

    Вызов этой функции позволяет получить для обработки следующее сообщение из очереди сообщений приложения. Данная функция копирует сообщение в структуру сообщения, на которую указывает указатель msg, и передает его в основной блок программы. Если значение следующего параметра NULL, то будут поступать сообщения, относящиеся к любому окну приложения. Значения последних параметров (0,0) указывает функции, что не надо применять никаких фильтров сообщений. Фильтры могут применяться для распределения получаемых сообщений по категориям, например, нажатия на клавишу или перемещение мыши. После входа в цикл обработки сообщений, выйти из него можно, получив только одно сообщение: WM_QUIT. Когда обрабатывается сообщение WM_QUIT, то возвращается значение "ложь" и цикл обработки сообщений завершается.

    Использование функции TranslateMessage

    Функция TranslateMessage(...) преобразует сообщения виртуальных клавиш в сообщения о символах.

    Использование функции DispatchMessage

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

    Оконная функция WndProc

    Вторая часть в любой программе под Windows – это оконная процедура. В рассматриваемом примере она маленькая (так как программа ничего не делает), но, именно эта часть и является самой главной и интересной в приложении.

    Рассмотрим функцию WndProc:

    LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
    {
      switch (Message)
      {
        case WM_DESTROY: PostQuitMessage(0); break;
        default: return DefWindowProc(hwnd, Message, wparam, lparam);
      }
      return 0;
    }

    Основное назначение оконной функции – это обработка сообщений Windows. Каждое приложение получает много сообщений. Их источник может быть разным. Например, сообщения от пользователя или от самой Windows. Обработка этих сообщений происходит именно в оконной функции. Это означает, что для каждого сообщения необходимо написать свой обработчик. Если обработчика не будет, то приложение не будет обращать внимание на сообщение. У оконной функции четыре параметра. Первый из них hwnd типа HWND задает окно, которое будет обрабатывать сообщение. Второй UINT Message – это передаваемое сообщение. Два последних WPARAM wparam, LPARAM lparam задают дополнительные параметры для передаваемого сообщения. Они для каждого сообщения свои. Оконная процедура отправляет сообщение в switch, который в примере имеет только один case:

    switch (Message)
    {
      case WM_DESTROY:
      ...

    То есть, пока рассматриваемая программа обращает внимание только на сообщение WM_DESTROY. Это сообщение окно получает только при своем уничтожении. После принятия этого сообщения необходимо вызвать функцию PostQuitMessage(...). В ответ на сообщение WM_DESTROY необходимо поместить в очередь сообщение WM_QUIT. Это и делает функция PostQuitMessage(...), посылая это сообщение в очередь и говоря, что процесс должен быть завершен. Если мы хотим, чтобы наша программа реагировала еще на что-нибудь, то надо написать еще case.

    Рассмотрим далее ветку default. В ней идет вызов функции DefWindowProc(hwnd, Message, wparam, lparam) . Основное предназначение этой функции – обработка сообщений Windows, которые не обрабатываются в нашей программе (то есть, для которых нет своего case ). При этом ничего не делается, но очередь из сообщений идет.

    Пример разложения в ряд функции. Графический вывод

    Проиллюстируем рассмотренный в предыдущем параграфе теоретичекий материал на примере написания Windows-приложения решения задачи о разложении в ряд некоторой функции.

    Задача: Разложить в ряд Тейлора в окрестности точки 0 функции .

    Напишем функции вычисления синуса и косинуса – разложения в ряд Тейлора в окрестности точки 0. Разложения в ряд имеют вид:

    $$sin(x)=x-\frac{x^3}{3!}+...+(-1)^{n-1}\cdot\frac{x^{2n-1}}{(2n-1)!}}$$ $$cos(x)=1-\frac{x^2}{2}+...+(-1)^{n}\cdot\frac{x^{2n}}{(2n)!}}$$

    Радиус сходимости для этих рядов – $$\infty$$.

    const double epsilon = 0.0000001;
    const double pi = 3.14159265;
    const double x_start = -2*pi;
    const double x_end = 2*pi;
    
    double sin_e(double arg, double eps)
    {
      double result = 0, rn = arg;
      for(int i = 1; ;i++)
      {
        if(rn < eps  rn > -eps) return result;  
        result += rn;
        rn = -rn*arg*arg/(i+1)/(i+2);  
        i++;  
      }
    }
    
    double cos_e(double arg, double eps)
    {
      double result = 0, rn = 1;
      for(int i = 0; ;i++)
      {
        if(rn < eps  rn > -eps) return result;  
        result += rn;
        rn = -rn*arg*arg/(i+1)/(i+2);  
        i++;  
      }
    }

    Для дальнейшего решения задачи – вывода графика функции в клиентском окне, будем обрабатывать сообщения, которые будут посылаться окну. Когда необходима перерисовка окна (изменились размеры окна, часть окна перекрылась другим окном и т.п.), система посылает окну сообщение WM_PAINT.

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

    Для построения изображения используем API функции операционной системы: MoveToEx(...), LineTo(...), GetClientRect(...) и т.д. Информацию об этих функциях можно получить в справочной системе.

    void DrawAxis(HDC hdc, RECT rectClient)
    {
      HPEN penGraph = CreatePen(PS_SOLID,2,RGB(0,0,255));
      HGDIOBJ gdiOld = SelectObject(hdc, penGraph);
      MoveToEx(hdc, 0, rectClient.bottom/2, NULL);
      LineTo(hdc, rectClient.right, rectClient.bottom/2);
      LineTo(hdc, rectClient.right - 5, rectClient.bottom/2 + 2);
      MoveToEx(hdc, rectClient.right, rectClient.bottom/2, NULL);
      LineTo(hdc, rectClient.right - 5, rectClient.bottom/2 - 2);
      MoveToEx(hdc, rectClient.right/2, rectClient.bottom, NULL);
      LineTo(hdc, rectClient.right/2, rectClient.top);
      LineTo(hdc, rectClient.right/2 - 2, rectClient.top + 5);
      MoveToEx(hdc, rectClient.right/2, rectClient.top, NULL);
      LineTo(hdc, rectClient.right/2 + 2, rectClient.top + 5);
      SelectObject(hdc, gdiOld);
    }
    
    void DrawGraph(HDC hdc, RECT rectClient)
    {
      HPEN penGraph = CreatePen(PS_SOLID,2,RGB(255,0,0));
      HGDIOBJ gdiOld = SelectObject(hdc, penGraph);
      double x_current = x_start;
      double step = (x_end - x_start)/rectClient.right;
      double y_start = cos_e(x_start, epsilon) + cos_e(x_start, epsilon);
      MoveToEx(hdc, 0, int(-y_start/step) + rectClient.bottom/2, NULL);
      while(x_current < x_end)
      {  
        x_current += step;
        double y_next = cos_e(x_current, epsilon) + cos_e(x_current, epsilon);
        LineTo(hdc, int(x_current/step) + rectClient.right/2, int(-y_next/step) + rectClient.bottom/2);
      }
      SelectObject(hdc, gdiOld);
    }
    
    void OnPaint(HWND hwnd)
    {
      PAINTSTRUCT ps;
      RECT rectClient;
      HDC hdc = BeginPaint(hwnd,ps);
      GetClientRect(hwnd, rectClient);
      DrawAxis(hdc, rectClient);
      DrawGraph(hdc, rectClient);
      ValidateRect(hwnd,NULL);
      EndPaint(hwnd,ps);
    }
    
    LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
    {
      switch (Message)
      {
        case WM_PAINT:
          OnPaint(hwnd);
        break;
        case WM_MOUSEMOVE:
          SetCapture(hwnd);
          SetCursor(LoadCursor(NULL,IDC_ARROW));
          ReleaseCapture();
        break;
        case WM_WINDOWPOSCHANGED:
          UpdateWindow(hwnd);
        break;
        case WM_DESTROY: PostQuitMessage(0); break;
        default: return DefWindowProc(hwnd, Message, wparam, lparam);
      }
      UpdateWindow(hwnd);
      return 0;
    }

    Функция :рис 1.1

    (рис 1.1) Построение графика функции
    Вернуться к учебному плану