Заголовочный файл WINDOWSX.H — КиберПедия 

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...

Типы сооружений для обработки осадков: Септиками называются сооружения, в которых одновременно происходят осветление сточной жидкости...

Заголовочный файл WINDOWSX.H

2020-03-31 495
Заголовочный файл WINDOWSX.H 0.00 из 5.00 0 оценок
Заказать работу

 

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

Например функция DeleteObject может применяться для удаления многих объектов GDI (Graphics Devices Interface) — карандашей, кистей, регионов и пр. По названию функции понять, какой–именно объект она удаляет нельзя, поэтому при чтении исходного кода приходится сосредотачиваться на параметрах этой функции. В windowsx.h определены макросы:

 

#define DeletePen(hpen) DeleteObject((HGDIOBJ)(HPEN)(hpen))
#define DeleteBrush(hbr) DeleteObject((HGDIOBJ)(HBRUSH)(hbr))
#define DeleteRgn(hrgn) DeleteObject((HGDIOBJ)(HRGN)(hrgn))

 

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

При включении файла windowsx.h в ваше приложение это надо делать после включения основного файла windows.h:

 

#define STRICT
#include <windows.h>
#include <windowsx.h>

 

Для того, что бы получить представление о возможностях windowsx.h рекомендуется посмотреть его исходный текст. В нем присутствуют следующие разделы:

· макросы для работы с функциями ядра (несколько макросов для работы с глобальной памятью)

· макросы для работы с объектами GDI

· макросы для работы с окнами (вызовы стандартных функций)

· распаковщики сообщений (самая большая часть)

· макросы для работы с окнами стандартных классов (кнопки, списки и пр.)

· некоторые макросы для оптимизации стандартной библиотеки времени выполнения

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

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

 

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

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

Вполне очевиден выход — разнести обработку сообщений по отдельным функциям, которые будут вызываться из процедуры обработки сообщений. Однако для каждого сообщения передаются свои данные, упакованные в двух параметрах — wParam и lParam. Иногда они не используются, иногда содержат какие–либо значения, иногда — указатели. Естественно, было бы удобным передавать в вызываемую функцию уже распакованные параметры. Затруднение здесь вызывает то, что для Windows API и для Win32 API одни и те же данные одного и того же сообщения могут быть упакованы по разному[18].

При разработке windowsx.h это все было учтено (для Windows API и для Win32 API распаковщики определяются по разному). Так, для каждого сообщения WM_xxx определен свой макрос с именем HANDLE_WM_xxx. Например, для сообщения WM_CREATE определен макрос:

 

HANDLE_WM_CREATE(hwnd, wParam, lParam, fn)

 

Параметры всех макросов одинаковые, что позволяет передавать им непосредственно параметры сообщения (окно–адресат hwnd, параметры wParam и lParam), а также имя функции–обработчика fn. Этот макрос должен использоваться внутри конструкции switch для вызова нужной функции и передачи ей распакованных параметров. Например фрагмент следующего вида:

 

switch (uMsg) {
case WM_CREATE: return HANDLE_WM_CREATE(hWnd,wParam,lParam,fnOnCreate);
//...
}

будет превращен компилятором в следующий фрагмент (подробнее см. исходный текст windowsx.h):

switch (uMsg) {
case WM_CREATE:
      return ((fnOnCreate)((hWnd),(LPCREATESTRUCT)(lParam))?
            0L: (LRESULT)-1L);
//...
}

 

То есть при раскрытии макроса HANDLE_WM_xxx осуществляется распаковка параметров, вызов функции и анализ возвращаемого результата. Здесь, кстати, скрыта одна ловушка (по счастью крайне редкая): результат, возвращаемой функцией–обработчиком не всегда будет совпадать с результатом, описанным в справочнике для данного сообщения. Случай с WM_CREATE именно такой — согласно описанию обработчик WM_CREATE должен вернуть 0L, если все в порядке. А, как мы видим в приведенном фрагменте, функция, вызываемая распаковщиком, должна вернуть TRUE, то есть не 0, если все в порядке (распаковщик сам заменит TRUE на 0L).

При рассмотрении этого примера возникает вопрос — а как должна быть описана функция–обработчик, что бы распаковщик ее правильно вызывал? Ответ прост — в самом файле windowsx.h перед определением соответствующего макроса приводится прототип этой функции. То есть нам надо сделать следующее: открыть windowsx.h, найти в нем строку, где определяется распаковщик для WM_CREATE (это легко делается поиском) и посмотреть на приведенный там текст:

 

/* BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) */
#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \
            ((fn)((hwnd), (LPCREATESTRUCT)(lParam))? 0L: (LRESULT)-1L)
#define FORWARD_WM_CREATE(hwnd, lpCreateStruct, fn) \
            (BOOL)(DWORD)(fn) ((hwnd), WM_CREATE, 0L, \
            (LPARAM)(LPCREATESTRUCT)(lpCreateStruct))

 

Описание функции Cls_OnCreate мы и ищем. Далее нам надо его просто скопировать в наше приложение и исправить при желании имя функции. Единственное, что остается не слишком удобным — так это вызов макроса–распаковщика — уж очень длинная строка получается. Для этого в windowsx.h содержится отдельный небольшой макрос:

 

HANDLE_MSG(hWnd, uMsg, fn)

 

Используется он таким способом:

 

switch (uMsg) {
HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);
//...
}

 

При этом он сам вставляет “case WM_xxx: return...” и прочее. Важно следить, что бы в описании оконной процедуры параметры wParam и lParam назывались именно так и не иначе. Дело в том, что HANDLE_MSG при обращении к макросу HANDLE_WM_xxx указывает ему именно эти имена.

Чтобы закончить разговор о распаковщиках сообщений надо ответить только на два вопроса — зачем нужны макросы FORWARD_WM_xxx, определенные в том–же windowsx.h и как можно добавить распаковщики для каких–либо сообщений, там не определенных (например, нестандартных).

Рассмотренные пока макросы–распаковщики позволяли нам вызвать функцию–обработчик сообщения, имея для нее данные, упакованные в wParam и lParam. Однако иногда возникает другая необходимость — имея параметры функции передать какое–либо сообщение. Для этого предназначены макросы FORWARD_WM_xxx. Для использования этих макросов, необходимо, что–бы функция, получающая параметры сообщения, имела следующий вид:

LRESULT proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

Макросы FORWARD_WM_xxx получают в качестве параметров распакованные данные (как и функция–обработчик), упаковывают их в параметры сообщения и вызывают указанную функцию. По счастью практически все функции, которые придется вызывать с помощью макросов FORWARD_WM_xxx (SendMessage, PostMessage, DefWindowProc и пр.) соответствуют приведенному описанию.

Например, сообщение WM_SETFONT посылается окну (стандартного класса) для того, что бы назначить ему нужный шрифт. Параметры этого сообщения следующие: wParam содержит хендл шрифта, а младшее слово lParam указывает, надо ли перерисовывать окно сразу после смены шрифта. Предположим, что ваше окно имеет дочернее окно, и вам хочется сделать так, чтобы при смене шрифта в вашем окне одновременно менялся шрифт в дочернем. Соответственно вы должны включить в оконную процедуру обработку сообщения WM_SETFONT и в его обработчике передать такое–же сообщение дочернему окну.

 

void Cls_OnSetFont(HWND hwnd, HFONT hfont, BOOL fRedraw);

LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
      switch (uMsg) {
      //...
      HANDLE_MSG(hWnd, WM_SETFONT, Cls_OnSetFont);
      //...
      }
}

//...

void Cls_OnSetFont(HWND hwnd, HFONT hfont, BOOL fRedraw)
{
      HWND hwndChild =...; // определение хендла дочернего окна
      FORWARD_WM_SETFONT(hwndChild, hfont, fRedraw, SendMessage);
}

 

Здесь, кстати, можно было бы воспользоваться макросом SetWindowFont из того же windowsx.h. Этот макрос обращается к FORWARD_WM_SETFONT, как в рассмотренном примере, однако текст при этом становится более читаемым:

void Cls_OnSetFont(HWND hwnd, HFONT hfont, BOOL fRedraw)
{
      HWND hwndChild =...; // определение хендла дочернего окна
      SetWindowFont(hwndChild, hfont, fRedraw);
}

Добавление собственных распаковщиков не должно вызвать больших затруднений — достаточно только разработать реализации макросов HANDLE_WM_xxx и FORWARD_WM_xxx аналогично уже сделанному в windowsx.h.

Пример 1B — использование распаковщиков сообщений

Этот пример иллюстрирует применение распаковщиков сообщений на примере простейшего приложения. Фактически он соответствует слегка измененному примеру 1A, в котором оконная процедура переписана для использования распаковщиков сообщений. Функция WinMain в этом примере осталась без изменений.

 

#define STRICT
#include <windows.h>
#include <windowsx.h>

#define   UNUSED_ARG(arg) (arg)=(arg)

static char szWndClass[] = "test window";

BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
UNUSED_ARG(hwnd);
UNUSED_ARG(lpCreateStruct);
return TRUE;
}

void Cls_OnPaint(HWND hwnd)
{
PAINTSTRUCT ps;

BeginPaint(hwnd, &ps);
TextOut(ps.hdc, 0, 0, "Hello, world!", 13);
EndPaint(hwnd, &ps);
}

void Cls_OnDestroy(HWND hwnd)
{
UNUSED_ARG(hwnd);

PostQuitMessage(0);
}

LRESULT WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
HANDLE_MSG(hWnd, WM_CREATE, Cls_OnCreate);
HANDLE_MSG(hWnd, WM_PAINT, Cls_OnPaint);
HANDLE_MSG(hWnd, WM_DESTROY, Cls_OnDestroy);
default: break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

static BOOL init_instance(HINSTANCE hInstance)
{
WNDCLASS        wc;

wc.style =       0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra =   0;
wc.cbWndExtra =      0;
wc.hInstance =     hInstance;
wc.hIcon =            LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor =  LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
return RegisterClass(&wc) == NULL? FALSE: TRUE;
}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
UNUSED_ARG(lpszCmdLine);
MSG    msg;
HWND hWnd;

if (!hPrevInst) {
      if (!init_instance(hInst)) return 1;
}

hWnd= CreateWindow(
      szWndClass,                          // class name
      "window header",                  // window name
      WS_OVERLAPPEDWINDOW,       // window style
      CW_USEDEFAULT,CW_USEDEFAULT, // window position
      CW_USEDEFAULT,CW_USEDEFAULT, // window size
      NULL,                             // parent window
      NULL,                             // menu
      hInst,                               // current instance
      NULL                              // user-defined parameters
);

if (!hWnd) return 1;

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

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

return msg.wParam;
}

Немного об объектах

 

Здесь мы рассмотрим некоторые основные особенности реализации объектно–ориентированного программирования в Windows. В последнее время получили огромное распространение библиотеки объектов для создания приложений в среде Windows. Особенно широко они стали распространяться с развитием систем визуального программирования. Наибольшее распространение получили библиотеки объектов компаний

· Borland — Object Windows Library (OWL), поддерживается компиляторами Borland C++ (рассматривается версия v2.5, сопровождающая компилятор Borland C/C++ v4.5).

· Microsoft — Microsoft Foundation Classes (MFC), поддерживается наибольшим количеством компиляторов, среди которых Microsoft Visual C++, Watcom C++, Symantec C++ и другие (рассматривается версия v4.0, сопровождающая Visual C/C++ v4.0).

Такие библиотеки достаточно многофункциональны и громоздки, размер исполняемого файла, созданного с их помощью редко бывает меньше 300–400K. Конечно, при разработке больших систем, поддерживающих такие инструменты как OLE, DAO или WOSE, регистрирующих свои собственные типы файлов и т.д., использование этих библиотек может существенно сократить время, необходимое для разработки приложения.

Эти библиотеки объектов, хотя и имеют огромное количество различий, неизбежно имеют и много общего, что определяется платформой, на которой они работают — Windows. Для обеспечения эффективной работы приложений эти библиотеки вынуждены предусматривать простой механизм доступа посредством методов объектов к функциям API, что их неизбежно сближает между собой. Кроме того реализация и иерархия объектов в Windows неизбежно приводит к появлению чем–то сходной иерархии классов в библиотеках ООП.

В этом разделе мы рассмотрим простейшее приложение в среде Windows, построенное средствами ООП, причем все классы будут оригинальными — ни MFC, ни OWL не применяется. Это сделано для того, что бы “извлечь” на поверхность некоторые аспекты разработки классов для Windows–приложений. Здесь будут использоваться существенно упрощенные методы реализации объектов, по сравнению с “большими” библиотеками.

Возможно, что в некоторых частных случаях использование такого подхода может оказаться и более продуктивным, чем применение MFC или OWL. Особенно, если ваше приложение похоже на простейшее “Hello, world!” (в этом случае, правда, еще удобнее может быть обойтись совсем без классов).

Особенности ООП в Windows

 

На самом деле Windows не является настоящей объектно–ориентированной средой. Хотя окно и может быть названо объектом ООП, но лишь с достаточной натяжкой. Самое существенное отличие окна в Windows от объекта ООП заключается в том, что сообщение, обрабатываемое оконной функцией, во многих случаях не выполняет действий, а является “информационным”, указывая на то, что над окном выполняется та или иная операция какой–либо внешней функцией.

Поясним это на примере создания окна. В случае ООП для уничтожения объекта он должен получить сообщение “destroy”, обработка которого приведет к его уничтожению. В Windows сообщение WM_DESTROY не выполняет никаких функций по уничтожению окна. Оно только информирует окно о том, что в это время окно уничтожается средствами обычной функциональной библиотеки, например посредством функции DestroyWindow. Вы можете вообще игнорировать это сообщение, возвращать любое значение, вызывать или не вызывать функцию обработки по умолчанию — окно все равно будет уничтожено.

С точки зрения реализации объектов это приводит к тому, что большая часть методов представлена в двух вариантах — один вызывает соответствующую функцию API, а другой вызывается при обработке соответствующего сообщения. Так в случае функции DestroyWindow и сообщения WM_DESTROY для класса, представляющего окно, будет существовать два метода: метод Destroy и метод OnDestroy (названия методов условны, в реальных библиотеках они могут отличаться). Метод Destroy будет соответствовать вызову функции DestroyWindow, а метод OnDestroy — обработчику сообщения WM_DESTROY.

На этом, к сожалению, сложности не кончаются. Допустим, что вы хотите сделать так, что бы окно не уничтожалось. На первый взгляд надо переопределить метод Destroy, что бы он не вызывал функцию DestroyWindow; при этом вызов метода Destroy не будет уничтожать окно. Однако все гораздо сложнее: окно по–прежнему может быть уничтожено прямым обращением к функции API — DestroyWindow. Более того, стандартные обработчики сообщений (то есть принадлежащие Windows, а не библиотеке классов) так и делают. Так стандартная обработка сообщения WM_CLOSE приводит к вызову функции DestroyWindow (а не метода Destroy). То есть для решения подобной задачи надо переопределить все методы объекта и все обработчики сообщений, которые ссылаются на соответствующую функцию API. Задача фактически не решаемая — особенно с учетом недостаточно подробной и точной документации.

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

Базовые классы приложения

 

Когда разрабатывается обычное приложение на C или C++ без использования классов, то надо разработать как функцию WinMain, определяющую работу приложения в целом, так и оконную процедуру, определяющую реакцию окна на внешние воздействия. При применении объектов эти функции возлагаются на методы классов. Естественно, что и в MFC, и в OWL существуют классы, предназначенные как для описания приложения в целом, так и для описания окон. Эти классы должны использоваться в качестве классов–предков, от которых порождаются классы, описывающие ваше приложение и его главное окно.

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

MFC

 

В библиотеке Microsoft Foundation Classes для описания приложения используются следующие классы:

Рисунок 5. Классы MFC, описывающие окно и приложение.

 

Данная версия MFC рассчитана на разработку многопотоковых приложений для Win32. Это наложило свой отпечаток на иерархию классов — в качестве одного из базовых выступает класс CWinThread, описывающий отдельный поток. И только на его основе построен класс CWinApp, описывающий приложение (в Win32 существует понятие основного потока, который обслуживает функцию WinMain, именно он и будет представлен объектом класса CWinThread, на основе которого порождается объект класса CWinApp).

OWL

В этой библиотеке иерархия классов несколько иная — библиотека рассчитана на разработку 16ти разрядных приложений Windows 3.x, не поддерживающую потоков.

 

Рисунок 6. Классы OWL, описывающие окно и приложение.

 

Окна ООП и окна Windows

 

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

· существуют методы классов, соответствующие вызову функций API;

· существуют методы классов, соответствующие обработчикам сообщений;

а, с другой стороны:

· существуют окна, созданные с помощью классов ООП;

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

Так, например, диалог “Открыть Файл” является стандартным диалогом Windows. Он создается и выполняется посредством вызова одной функции API — FileOpen. Эта функция сама, независимо от приложения и его библиотеки классов, создает необходимые окна и работает с ними. Однако у программиста может возникнуть необходимость как–то взаимодействовать с этим диалогом в процессе его работы.

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

1. должна быть вызвана функция API для окна, реализованного как объект класса;

2. должна быть вызвана функция API для окна, не являющегося объектом класса;

3. окно, реализованное как объект класса, получает сообщение — то есть надо вызвать соответствующий метод–обработчик этого сообщения;

4. окно, не являющееся объектом класса, получает сообщение.

Случаи 1 и 2 решаются сравнительно просто — среди членов–данных класса должен присутствовать член, задающий хендл окна в Windows. В таком случае вызов функций API, нуждающихся в хендле, происходи элементарно. Небольшой нюанс связан с окнами, не являющимися объектами класса. Например, диалоги, включая стандартные и их элементы управления — кнопки, флажки и прочее, часто создаются как окна, принадлежащие Windows. То есть первоначально, в момент их создания, не существует объектов приложения, соответствующих этим окнам. Для этого в библиотеку вводятся средства создания объектов по хендлу. Эти средства могут несколько различаться в разных библиотеках.

Например метод CWnd* CWnd::FromHandle(HWND), существующий в MFC, создает специальный объект, описывающий окно, связывает его с указанным хендлом и возвращает указатель на него. Этот объект считается “временным” — спустя некоторое время MFC сама его уничтожит. В OWL аналогичного эффекта можно добиться используя специальную форму конструктора объекта TWindow.

Случай 3 существенно более сложный. Когда окно получает сообщение, Windows вызывает зарегистрированную оконную процедуру, причем для этой процедуры передается только хендл окна и параметры сообщения. Указатель на объект приложения, описывающего это окно, остается в момент получения сообщения неизвестным!

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

· находит связанное с данным хендлом окно — для этого библиотеки классов поддерживают специальные таблицы соответствия хендлов окон описаниям этих окон в приложении

· распределяет пришедшее сообщение соответствующему (обычно виртуальному) методу–обработчику этого сообщения.

Для задания методов–обработчиков конкретных сообщений вводятся специальные таблицы отклика или таблицы трансляции сообщений (response table, message map table). Когда вы разрабатываете новый класс окон, вы для него должны разработать такую таблицу, в которой должны быть указаны соответствия приходящих сообщений и вызываемых методов (конечно, если это не сделано в классе–предке).

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

Это накладывает ограничения на применение методов–обработчиков сообщений — для окон, не созданных как объекты класса, эти методы вызываться не будут. В случае MFC названия таких методов обычно начинаются на On..., например OnDestroy; а в случае OWL — на Ev..., например EvDestroy. Часто можно так организовать приложение, что переопределять эти методы просто не потребуется, однако это не всегда удобно и возможно.

При необходимости как–либо изменить реакцию окна на внешние события (переопределить принятую обработку сообщений) надо, во–первых, создать соответствующий объект класса (как в случае 2). Во–вторых обычное окно, создаваемое Windows (например, какой–либо элемент управления диалогом — кнопка, флажок и пр.) или другим приложением, использует собственную оконную процедуру. Эта процедура, естественно, никак не связана с библиотекой ООП, применяемой вашим приложением. Таким образом, при получении окном сообщений, вызывается только лишь его собственная оконная процедура, не обращающаяся к методам класса. То есть необходимо осуществить подмену оконной процедуры (в Windows это называется порождением подкласса окон — subclass) с помощью специальных методов библиотек, выполняющих эту операцию: SubclassWindowFunction в OWL или SubclassWindow в MFC. После этого новая оконная функция будет обращаться к методам класса для обработки сообщений, а в качестве стандартной обработки будет использоваться та оконная функция, которая использовалась окном до ее подмены.

Однако при использовании этого приема необходимо учитывать следующие нюансы:

· при создании объекта класса лучше использовать один из базовых классов (CWnd или TWindow), так как все порожденные от них классы переопределяют значительно большее число методов, предполагая стандартную обработку сообщений, реализованную в DefWindowProc, а не в той процедуре, которую вы подменили. Это может привести к конфликтам между новой обработкой событий и прежней оконной процедурой. Особенно опасна ошибка в назначении класса — библиотека классов и компилятор никак не смогут проверить вас и предупредить, если вы, скажем, для кнопки, создадите объект класса “список” (LISTBOX). При такой ошибке конфликт практически неизбежен. В любом случае надо хорошо представлять себе, для какой стандартной оконной процедуры реализован какой класс библиотеки ООП и обработку каких сообщений он переопределяет, прежде чем решиться на подмену оконной процедуры.

· в случае Win32 для окон, созданных другим приложением, оконные процедуры (используемая окном и назначаемая вами) размещается в различных адресных пространствах разных процессов. Обращение из другого процесса по новому адресу функции приведет, скорее всего, к ошибке — так как этот адрес задан в адресном пространстве вашего приложения, а что находится в адресном пространстве другого процесса по этому адресу вам неизвестно. Решить эту проблему можно, выделяя описание объекта класса и его процедуры в отдельную DLL, а затем внедряя ее в адресное пространство процесса, создавшего окно. Однако этот прием существенно сложнее.

Пример 1C — использование собственных классов

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

Коротко рассмотрим реализацию этого способа: вместо ведения таблиц соответствия хендлов объектам приложения можно хранить необходимые данные непосредственно в структуре описания окна в Windows (см. “Регистрация класса окон”). Так как доступ к этим данным осуществляется только с помощью функций, то размещать там все описание окна нецелесообразно, зато в этой структуре можно разместить указатель на связанный объект. Отсюда следует ограничение — этот метод будет работать только с теми окнами, в структуре описания которых в Windows зарезервировано специальное поле для указателя. Это могут быть только окна, созданные нами.

 

Рисунок 7. Поиск метода–обработчика сообщения в примере.

 

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

Кроме того, для упрощения в примере остались некоторые следы обычного программирования — осталась, хотя и сильно измененная, функция WinMain, в которой создается объект “приложение”.

Рассматриваемый пример состоит из 3х файлов: 1c.h — общий заголовочный файл, содержащий описания базовых классов; 1c_cls.cpp — методы и статические данные базовых классов; 1c_main.cpp — собственно само приложение: описание собственных классов и их методов, а также функция WinMain.

 

Файл 1c.h

 

#define STRICT
#include <windows.h>

#define UNUSED_ARG(arg) (arg)=(arg)

class Win0 {
 protected:
HWND hwnd;  

virtual LRESULT dispatch(UINT, WPARAM, LPARAM);
virtual BOOL  OnCreate(LPCREATESTRUCT);
virtual void     OnDestroy(void) = 0;
virtual void     OnPaint(HDC hdc) = 0;  

 public:
Win0(void);
~Win0(void);
BOOL       create(char*);
void           destroy(void);
void           update(void) { UpdateWindow(hwnd); }
void           show(int nCmdShow) { ShowWindow(hwnd, nCmdShow); }

friend LONG WINAPI _export Win0proc(HWND, UINT, WPARAM, LPARAM);
};

class App0 {
 public:
static HINSTANCE hInstance;
static HINSTANCE hPrevInstance;
static LPSTR  lpszCmdLine;
static int          nCmdShow;

App0(HINSTANCE, HINSTANCE, LPSTR, int);
~App0(void);

BOOL       init(void);
int             run(void);
void           release(void);
};

 

Файл 1c_cls.cpp

 

#include "1c.h"
HINSTANCE App0::hInstance;
HINSTANCE App0::hPrevInstance;
LPSTR      App0::lpszCmdLine;
int       App0::nCmdShow;

static char   szWndClass[]= "test window class";
static Win0* on_create_ptr;

Win0::Win0(void)
{
hwnd = NULL;
}

Win0::~Win0(void)
{
destroy();
}

LRESULT WINAPI _export Win0proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Win0* pwin;

pwin = (Win0*)GetWindowLong(hWnd, 0);
if (!pwin) {
      SetWindowLong(hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr));
      pwin->hwnd = hWnd;
}
return pwin->dispatch(uMsg, wParam, lParam);
}

LRESULT Win0::dispatch(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT   ps;  

switch (uMsg) {
case WM_CREATE: return OnCreate((LPCREATESTRUCT)lParam)? 0L: -1L;
case WM_PAINT: OnPaint(BeginPaint(hwnd, &ps)); EndPaint(hwnd, &ps); return 0L;
case WM_DESTROY: OnDestroy(); return 0L;
default: break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void Win0::destroy(void)
{
if (IsWindow(hwnd)) DestroyWindow(hwnd);
hwnd = (HWND)NULL;
}

BOOL Win0::create(char* title)
{
on_create_ptr = this;
CreateWindow(
      szWndClass,                          // class name
      title,                                 // window name
      WS_OVERLAPPEDWINDOW,       // window style
      CW_USEDEFAULT,CW_USEDEFAULT, // window position
      CW_USEDEFAULT,CW_USEDEFAULT, // window size
      NULL,                             // parent window
      NULL,                             // menu
      hInstance,                        // current instance
      NULL                              // user-defined parameters
);
on_create_ptr = (Win0*)NULL;
return IsWindow(hwnd);
}

BOOL Win0::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
UNUSED_ARG(lpCreateStruct);

return TRUE;
}

App0::App0(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpszCmd, int nShow)
{
hInstance =    hInst;
hPrevInstance = hPrev;
lpszCmdLine =     lpszCmd;
nCmdShow =      nShow;
}

App0::~App0(void)
{
}

BOOL App0::init(void)
{
static BOOL    done;
WNDCLASS  wc;

if (!done &&!hPrevInstance) {
      wc.style =       0;
      wc.lpfnWndProc = Win0proc;
      wc.cbClsExtra = 0;
      wc.cbWndExtra = sizeof(LONG);
      wc.hInstance =     hInstance;
      wc.hIcon =            LoadIcon(NULL, IDI_APPLICATION);
      wc.hCursor =  LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
      wc.lpszMenuName = NULL;
      wc.lpszClassName = szWndClass;
      done = RegisterClass(&wc)? TRUE: FALSE;
}

return done;
}

int App0::run(void)
{
MSG msg;

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

return msg.wParam;
}

void App0::release(void)
{
}

 

Файл 1c_main.cpp

 

#include "1c.h"

class MainWindow: public Win0 {
 protected:
virtual void OnDestroy(void);
virtual void OnPaint(HDC hdc);

 public:
MainWindow(void);
~MainWindow(void);
};

class MyApp: public App0 {
 protected:
MainWindow wnd;

 public:
MyApp(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow);
~MyApp(void);
BOOL init(void);
};

MainWindow::MainWindow(void): Win0()
{
}

MainWindow::~MainWindow(void)
{
}

void MainWindow::OnDestroy(void)
{
PostQuitMessage(0);
}

void MainWindow::OnPaint(HDC hdc)
{
TextOut(hdc, 0, 0, "Hello, world!", 13);
}

MyApp::MyApp(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
: App0(hInst, hPrevInst, lpszCmdLine, nCmdShow)
{
}

MyApp::~MyApp(void)
{
}

BOOL MyApp::init(void)
{
if (App0::init()) {
      if (wnd.create("window header")) {
            wnd.show(nCmdShow);
            wnd.update();
            return TRUE;
      }
}
return FALSE;
}

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
int a;
MyApp app(hInst, hPrevInst, lpszCmdLine, nCmdShow);

if (app.init()) {
      a = app.run();
} else a = -1;
app.release();

return a;
}

 

Обзор примера 1C

 

Пример содержит два базовых класса: App0 — описывает приложение и Win0 — описывает окно.

Класс App0 содержит 4 члена–данных: hInstance, hPrevInstance, lpszCmdLine и nCmdShow, которые являются аргументами функции WinMain. Интереснее разобраться с методами, описанными в этом классе. Конструктор просто инициализирует члены–данные для использования в последующем; деструктор вообще ничего не делает. Пара методов init и release предназначена для переопределения в дальнейшем — метод init должен выполнять специфичную инициализацию приложения, а метод release — операции при завершении. В классе App0 метод init осуществляет регистрацию оконной процедуры (в терминологии Windows — класса), которая будет применяться данным приложением. Метод run выполняет цикл обработки сообщений.

Класс Win0 содержит только один член–данные hwnd — хендл окна. Конструктор устанавливает значение хендла окна равным NULL (окно не создано), деструктор проверяет существование окна и, при необходимости, закрывает его. Методы create, destroy, update и show соответствуют функциям API: CreateWindow, DestroyWindow, UpdateWindow и ShowWindow. Методы OnCreate, OnDestroy и OnPaint соответствуют обработчикам сообщений WM_CREATE, WM_DESTROY и WM_PAINT. Метод dispatch является диспетчером, который распределяет пришедшие сообщения по соответствующим методам–обработчикам.

В том–же классе декларирована дружественная функция Win0proc, которая является собственно оконной процедурой.

Коротко рассмотрим, как создается окно в этом примере. Для создания окна необходимо вызвать метод create, который, в свою очередь, вызовет функцию CreateWindow из Windows. Во время создания окна его оконная процедура начнет получать сообщения (в том числе и WM_CREATE, хотя, на самом деле, это будет не первое полученное сообщение). Эта процедура для нормальной работы требует, что бы в структуре описания окна в Windows был сохранен указатель на объект, описывающий окно в приложении. Но в момент первого вызова обработчика сообщений этот указатель там не находиться — все происходит еще только во время работы функции CreateWindow. Соответственно мы используем некоторую статическую переменную (on_create_ptr), которая перед вызовом CreateWindow инициализируется указателем на объект. Тогда обработчик сообщений может быть построен по следующей схеме:

 

LONG WINAPI _export Win0proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
      Win0* pwin;

   pwin = (Win0*)GetWindowLong(hWnd, 0); // получаем указатель на объект
      if (!pwin) {         // указатель равен NULL — объект только создается
            // инициализируем объект и указатель на него
            SetWindowLong(hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr));
            pwin->hwnd = hWnd;
      }
      // вызываем виртуальную функцию-диспетчер
      return pwin->dispatch(uMsg, wParam, lParam);
}

 

При нормальной работе первый вызов функции GetWindowLong вернет указатель на объект, так что следующий шаг — вызов функции–диспетчера. Таким образом дополнительные затраты ресурсов на реализацию ООП таким способом оказываются минимальными. В случае разработки классов–наследников от Win0 надо разработать собственную функцию–диспетчер, которая будет вместо процедуры DefWindowProc вызывать диспетчер класса–предка.

В примерах, сопровождающих компиляторы инициализация объекта и указателя на объект в структуре описания окна выполняется при обработке WM_CREATE. Это решение не является наилучшим — сообщение WM_CREATE далеко не самое первое из обрабатываемых сообщений, хотя, предусмотрев обработку сообщений с помощью DefWindowProc при неопределенном указателе, можно осуществлять инициализацию и при обработке WM_CREATE.

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

 

Основы работы с памятью

 

Дополнительно надо разобраться с несколькими терминами Windows API, которые постоянно применяются, но очень плохо описаны в документации. Речь идет о хендлах копии приложения (HINSTANCE), модуля (HMODULE) и задачи (HTASK). Все эти хендлы используются разными функциями, причем разница между ними никак не поясняется. Помимо этого в Win32 API появилась пара дополнительных хендлов — хендл процесса и хендл потока, а также идентификаторы процесса и потока. При этом осталась все прежние понятия, часто изменившие смысл, но в документации по–прежнему не описанные (или описанные плохо).

Хендл задачи (HTASK), хендлы и идентификаторы процесса и потока 

В Windows 3.x под задачей подразумевается конкретный запущенный процесс, для которого определены командная строка, текущая выполняемая инструкция, указатель на стек, переменные окружения, PDB (эквивалент префикса задачи (PSP) в среде DOS) и пр. Хендл задачи можно получить с помощью функции

HTASK GetCurrentTask(void);

В Win32 хендл задачи не применяется, а вместо него надо пользоваться хендлами и/или идентификаторами процесса и потока. Их можно получить с помощью фун<


Поделиться с друзьями:

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

Наброски и зарисовки растений, плодов, цветов: Освоить конструктивное построение структуры дерева через зарисовки отдельных деревьев, группы деревьев...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.155 с.