Рефераты
 

Ознакомление с приложениями Windows

p align="left">Обычно в проект включаются следующие файлы:

исходные тексты на C и C++ -- файлы с расширениями .c, .cpp, .c++;

файл, содержащий ресурсы приложения, как правило только один, либо .rc либо .res;

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

дополнительные объектные файлы .obj -- отдельно включаются очень редко;

файл описания модуля .def, только один, часто только при желании описать нестандартные параметры компоновки (см. ниже, в описании этого файла).

Файл описания модуля (.def)

Файл описания модуля (приложения) содержит обычный текст, который можно написать любым текстовым редактором. В качестве примера рассмотрим один из файлов описания модуля, использованный для построения приложения testapp.exe:

NAME TESTAPP
DESCRIPTION 'TESTAPP - test application'
EXETYPE WINDOWS
PROTMODE
STUB 'WINSTUB.EXE'
CODE LOADONCALL MOVEABLE
DATA MOVEABLE MULTIPLE
STACKSIZE 8192
HEAPSIZE 4096

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

Большинство компиляторов могут использовать собственные директивы, а также собственные расширения для параметров, задаваемых в директивах, не описанные в общих руководствах (как, к примеру, директива PROTMODE в приведенном примере). Кроме того список возможных директив в файлах описания модулей для Windows API и для Win32 API различается.

Windows API

Первая строка обычно задает имя модуля. Если вы строите приложение, то первой должна стоять директива NAME, а если динамически загружаемую библиотеку -- LIBRARY. Указание обоих директив считается некорректным. Имя должно соответствовать имени файла, так для testapp.exe эта строка будет такой: NAME TESTAPP, а для mydll.dll -- LIBRARY MYDLL.

LIBRARY dllname
NAME exename

Обычно следующая строка -- описание данного модуля. Она начинается с директивы DESCRIPTION, за которой следует произвольный текст, заключенный в апострофы:

DESCRIPTION `description text for module'

Следующая директива, если и присутствует, то всегда определяет, что данный модуль предназначен для работы в среде Windows (аналогичные файлы описания модулей могут применяться для OS/2).

EXETYPE WINDOWS

Еще две директивы предназначены для задания размеров стека и кучи. Задание размера стека менее 5K приводит к тому, что Windows сам увеличивает его до 5K. Задание размера кучи вообще абстрактно -- главное, что бы не 0, так как Windows будет увеличивать кучу при необходимости (вплоть до наибольшего размера сегмента данных приложения -- 64K). Размер кучи 0 говорит о том, что она просто не используется..

HEAPSIZE size
STACKSIZE size

Очень любопытная директива -- STUB. О ней надо рассказать чуть подробнее. Ранее было отмечено, что для Windows-приложений был разработан собственный формат выполняемого модуля. Очевидно, что попытка запустить такой модуль на старой версии DOS, который не рассчитан на такую возможность, приведет к грубой ошибке, вплоть до зависания компьютера или порчи данных. Чтобы этого избежать, сделали так -- Windows-приложение состоит из двух частей:

Первая часть представляет собой небольшое приложение MS-DOS, называемую заглушкой (stub). Обычно это приложение просто пишет на экране фразу типа “This program must be run under Microsoft Windows.”. Заголовок этой заглушки чуть изменен, чтобы Windows мог отличить DOS-приложение от Windows-приложения, но это изменение находится в неиспользуемой MS-DOS'ом части заголовка.

STUB `stubexe.exe'

Здесь stubexe.exe -- имя приложения-заглушки (возможно полное имя, вместе с путем)

Вторая часть -- собственно код и данные Windows-приложения с собственным заголовком.

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

Еще три директивы используются для описания параметров сегментов кода и данных. Директива CODE задает характеристики сегментов кода, DATA -- сегментов данных, а SEGMENTS позволяет описать характеристики для конкретного сегмента (в квадратные скобки [] заключены необязательные параметры, знак | разделяет возможные варианты; запись [FIXED|MOVEABLE] означает, что может быть указано либо FIXED, либо MOVEABLE, либо ничего). Более подробно о характеристиках сегментов приложения см. в описании диспетчера памяти Windows 3.x.

CODE [FIXED|MOVEABLE] [DISCARDABLE] [PRELOAD|LOADONCALL]

DATA [NONE|SINGLE|MULTIPLE] [FIXED|MOVEABLE]

SEGMENTS
segname [CLASS `clsname'] [minalloc] [FIXED|MOVEABLE] [DISCARDABLE] [SHARED|NONSHARED] [PRELOAD|LOADONCALL]
...

Могут быть указаны следующие параметры: FIXED или MOVEABLE -- сегмент фиксирован в памяти или перемещаемый; если сегмент перемещаемый, то он может быть теряемым (DISCARDABLE). Параметры PRELOAD и LOADONCALL указывают, когда сегмент должен быть загружен в оперативную память -- при загрузке приложения (PRELOAD) или при непосредственном обращении к нему (LOADONCALL). Параметры NONE, SINGLE или MULTIPLE применяются только для сегментов данных. NONE или SINGLE применяется для динамически загружаемых библиотек; NONE -- библиотека не имеет сегмента данных вообще, SINGLE -- сегмент данных присутствует в памяти в единственном экземпляре (динамические библиотеки загружаются только один раз, других копий не существует, все приложения ссылаются на одну библиотеку с единым сегментом данных). MULTIPLE -- сегмент данных загружается для каждой копии приложения, применяется только для приложений.

Директива SEGMENTS описывает характеристики конкретных сегментов; segname -- имя сегмента, clsname -- имя класса сегмента, minalloc -- минимальный размер сегмента. SHARED или NONSHARED сообщает, является ли сегмент разделяемым разными копиями приложения, либо нет. После одной директивы SEGMENTS может размещаться описание нескольких сегментов, по одному на каждой строке.

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

EXPORTS
exportname [=internalname] [@id] [RESIDENTNAME] [NODATA] [argsize]
...

В разделе EXPORTS перечисляются имена функций, экспортируемых данным модулем. Параметр exportname задает внешнее имя функции, под которым она будет доступна другим модулям, параметр internalname используется, если внешнее и внутреннее имена различаются, @id задает идентификатор функции, argsize -- если указан, сообщает сколько слов в стеке занимает список аргументов функции. Параметры RESIDENTNAME и NODATA используются крайне редко; RESIDENTNAME говорит о том, что имя функции должно размещаться в так называемом резидентном списке имен (который находиться в памяти постоянно после загрузки модуля), обычно имена размещаются в нерезидентном списке, который загружается при необходимости. NODATA говорит о том, что функция использует сегмент данных вызывающего модуля, а не экспортирующего (подробнее -- при разговоре о диспетчере памяти).

IMPORTS
[internalname=] modulename.id
[internalname=] modulename.importname

Раздел IMPORTS задает соответствие внутренних имен импортируемых функций (internalname) функциям, экспортированным другими модулями. Возможно два метода связывания имен -- по идентификатору (первый пример), modulename -- имя экспортирующего модуля, id -- идентификатор; либо по именам (второй пример), importname -- внешнее имя функции, под которым она была экспортирована другим модулем.

Win32 API

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

Так как файл описания модуля используется для создания DLL, то первая директива -- LIBRARY. Часто применяется также директива EXPORTS для задания идентификаторов экспортируемых функций (обе -- см. в описании файла описания модуля для Windows API).

Для задания нестандартных параметров отдельных сегментов используется директива SECTIONS, заменяющая прежнюю директиву SEGMENTS. Синтаксис директивы SECTIONS несколько иной, хотя допускается замена слова SECTIONS на SEGMENTS:

SECTIONS
secname [CLASS `classname'] [EXECUTE] [READ] [WRITE] [SHARED]

После указания имени секции (сегмента) следует необязательное указание имени класса и атрибутов этой секции -- разрешение на выполнение (EXECUTE), на чтение (READ), на запись (WRITE) и объявление секции разделяемой (SHARED) между разными копиями модуля (загруженными в разных адресных пространствах разных приложений!).

Дополнительные разделы

В этом разделе будет рассказано о малоизвестном заголовочном файле -- windowsx.h. В некоторых случаях разработчики его, конечно, используют, но редко больше чем на 5% от его возможностей. Этот заголовочный файл был разработан специально для облегчения контроля исходного текста программы. К сожалению, в большей части документации, сопровождающей компиляторы, этот файл вообще не описан, хотя существует уже очень давно. Пожалуй впервые он описан в документации, сопровождающей Microsoft Visual C++ 4.0 (Microsoft Developer Studio, раздел “SDKs|Win32 SDK|Guides|Programming Techniques|Handling Messages with portable macros”). Однако даже там описаны только принципы его применения, но не дано подробное описание всех его макросов. Как результат -- при применении windowsx.h приходится постоянно обращаться к его исходному тексту.

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

В 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 одни и те же данные одного и того же сообщения могут быть упакованы по разному Это связано с тем, что параметр wParam имеет тип UINT -- то есть он в Windows API представляет собой 16ти разрядное слово, а в Win32 API -- 32х разрядное. Кроме того, в младшем или старшем слове lParam часто размещается хендл, который в Windows API 16ти разрядный. К сожалению в Win32 API хендлы 32х разрядные, так что уместить его на прежнем месте не представляется возможным..

При разработке 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. Эта функция сама, независимо от приложения и его библиотеки классов, создает необходимые окна и работает с ними. Однако у программиста может возникнуть необходимость как-то взаимодействовать с этим диалогом в процессе его работы.

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

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

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

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

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

Случаи 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 хендл задачи не применяется, а вместо него надо пользоваться хендлами и/или идентификаторами процесса и потока. Их можно получить с помощью функций:

HANDLE GetCurrentProcess( void );
HANDLE OpenProcess( fdwAccess, fInherit, dwIDProccess );
DWORD GetCurrentProcessId( void );
HANDLE GetCurrentThread( void );
DWORD GetCurrentThreadId( void );

Функции GetCurrentProcess и GetCurrentThread возвращают так называемый псевдодескриптор Это просто некоторая константа, используемая для обозначения «текущего потока» или «текущего процесса». процесса (потока). Псевдодескриптор -- это некоторая величина, рассматриваемая в качестве дескриптора текущего процесса (потока). То есть эта величина, применяемая в контексте другого процесса (потока), будет описывать его, а не данный поток. Для получения “настоящего” хендла из псевдодескриптора надо воспользоваться функцией:

BOOL DuplicateHandle(
hSourceProcess, hSourceHandle, hTargetProcess, lphTargetHandle,
fdwAccess, fInherit, fdwOptions
);

Хендл копии приложения (HINSTANCE) ?

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

int GetInstanceData( hInstance, pByte, cbData );

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

В Win32 для каждого запущенного приложения (т.е. процесса) выделяется виртуальное адресное пространство в 4Г в едином сегменте. Поэтому данный хендл соответствует не сегменту данных (который описывает весь 4Г сегмент), а адресу в виртуальном пространстве, с которого был загружен данный модуль. В адресном пространстве одного процесса никаких других приложений не существует, поэтому этот хендл не может применяться для обнаружения других копий приложения и тем более для обмена данными между разными копиями приложений. В приложениях Win32 hPrevInstance всегда равен NULL, а хендл текущей копии приложения в большинстве случаев совпадает. При необходимости обнаружения других копий приложения надо использовать какие-либо иные методы, например функцию:

HWND FindWindow( lpszClassName, lpszWindowTitle );

Хендл окна в Win32 является уникальным и может идентифицировать конкретное окно в любом приложении.

Для обмена данными между приложениями (процессами) приходится передавать данные из адресного пространства одного процесса в адресное пространство другого. Для выполнения этих операций предусмотрено сообщение WM_COPYDATA. Когда Вы посылаете это сообщение окну, созданному другим процессом, указанные Вами данные копируются в адресное пространство другого процесса и могут быть прочитаны оконной процедурой окна-получателя. Этот механизм может применяться и для обмена данными между 16ти и 32х битовыми приложениями, однако для этого необходимо определить номер сообщения WM_COPYDATA и специальную структуру COPYDATASTRUCT для 16ти битовой платформы -- так как файл windows.h не содержит этих определений:

#define WM_COPYDATA 0x004A

typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
LPVOID lpData;
} COPYDATASTRUCT, FAR* PCOPYDATASTRUCT;

Хендл модуля (HMODULE) ?

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

HMODULE GetModuleHandle( lpszFileName );
int GetModuleFileName( hInstance, lpsBuffer, cbMaxSize );

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

Подробнее о приложении (2)

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

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

Обычно, хотя это и не обязательно, функция WinMain реализует следующую схему:

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

создается главное окно приложения, для чего часто регистрируется новый класс окон (оконная функция);

организуется цикл обработки сообщений приложения. Обычно цикл завершается при закрытии главного окна приложения;

после завершения цикла обработки сообщений выполняется освобождение занятых ресурсов, после чего функция WinMain заканчивается.

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

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

Замечание 3. В момент вызова функции WinMain ей, через аргументы, передается несколько параметров, например хендл копии приложения hInstance. До вызова WinMain приложение “не знает” этих данных. Поэтому могут возникать сложности с использованием статических конструкторов объектно-ориентированных языков (C++).

!!!!!!!!Фокус ввода!!!!!!

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

Максимизированном, то есть быть “распахнутым” на весь экран -- при этом внутренняя область окна занимает весь экран, кроме небольших полос сверху -- где размещается заголовок и меню, снизу -- горизонтальная полоса прокрутки и справа -- вертикальная полоса прокрутки; рамка окна находится за пределами экрана, мы ее не видим, перемещение окна невозможно.

Для максимизации окна мы можем воспользоваться функцией ShowWindow со следующими возможными параметрами:

ShowWindow( hWnd, SHOW_FULLSCREEN );
ShowWindow( hWnd, SW_SHOWMAXIMIZED );
ShowWindow( hWnd, SW_MAXIMIZE );

максимизированное окно всегда активно и имеет фокус ввода. Когда какое-либо окно максимизируется, все остальные верхние окна получают сообщение WM_SIZE, информирующее о том, что они “закрыты” сверху максимизированным окном.

Мы можем узнать, является ли наше окно максимизированным с помощью функции

BOOL IsZoomed( hWnd );

При использовании системного меню операции максимизации окна соответствует пункт Maximize, выбор которого порождает системную команду SC_MAXIMIZE (или синоним SC_ZOOM). (см. сообщение WM_SYSCOMMAND)

Здесь вместо термина maximize может использоваться zoom.

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

ShowWindow( hWnd, SHOW_ICONWINDOW );
ShowWindow( hWnd, SW_SHOWMINIMIZED );
ShowWindow( hWnd, SW_SHOWMINNOACTIVE );
ShowWindow( hWnd, SW_MINIMIZE );
CloseWindow( hWnd );

Разные способы, использующие ShowWindow, отличаются только правилами активации окна. SW_SHOWMINIMIZED и SHOW_ICONWINDOW отображает окно в виде пиктограммы, делая его активным; SW_SHOWMINNOACTIVE не изменяет текущего активного окна; SW_MINIMIZE (как и функция CloseWindow) делает активным следующее окно в списке Windows. Последний способ эффективен при минимизации главного окна приложения -- так как минимизированное главное окно обычно обозначает передачу активности другому приложению.

Проверить состояние окна можно с помощью функции

BOOL IsIconic( hWnd );

При использовании системного меню превращению окна в иконку соответствует пункт Minimize, порождающий системную команду SC_MINIMIZE (или синоним SC_ICON). (см. сообщение WM_SYSCOMMAND)

В этом случае используется сразу три разных термина для обозначения одного и того-же: minimize, close и iconic. При этом функция CloseWindow является единственной, интерпретирующей термин close таким способом; в остальных случаях close означает действительно закрытие (иногда уничтожение) окна. Здесь же надо, что термин open, применяемый к минимизированному окну обозначает его максимизацию или восстановление нормальных размеров.

Нормальным, то есть мы видим (или можем увидеть) его рамку, мы можем перемещать окно по экрану. Когда окно находится в нормальном состоянии, то для него определены максимально и минимально допустимый размеры. Эти размеры нельзя путать с максимизированным и минимизированным состояниями. Максимальный размер нормального окна может даже превышать размер окна в максимизированном состоянии, минимальный размер это обычно такой размер, при котором окно еще может быть корректно представлено в виде окна.

Для перехода из минимизированного состояния к нормальному можно воспользоваться функцией

OpenIcon( hWnd );

или, как из минимизированного, так и из максимизированного состояния можно пользоваться функцией ShowWindow с параметрами:

ShowWindow( hWnd, SHOW_OPENWINDOW );
ShowWindow( hWnd, SW_SHOWNORMAL );
ShowWindow( hWnd, SW_RESTORE );
ShowWindow( hWnd, SW_SHOWNOACTIVATE );

В документации (SDK Help) указано, что SW_RESTORE и SW_SHOWNORMAL эквивалентны, но это далеко не так -- SW_RESTORE восстанавливает предыдущее состояние, а не нормальное. То есть, если Вы минимизировали окно из максимизированного, то SW_RESTORE вернет Вас к максимизированному окну, а SW_SHOWNORMAL -- к нормальному. SW_SHOWNORMAL имеет синоним SHOW_OPENWINDOW.

Если окно восстанавливается или максимизируется из минимизированного состояния, то Ваше окно получит сообщение WM_QUERYOPEN -- обрабатывая которое Вы можете разрешить или запретить дальнейшие действия. Если Вы возвращаете TRUE, то окно будет раскрыто, а если Вы вернете FALSE, то окно останется минимизированным.

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

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

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

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

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

Настройка приложений

Под настройкой (иногда "профилированием") понимается задание характеристик приложения и их сохранение для использования при следующем запуске.

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

Такие файлы (обычно имеющие расширение .INI) являются обычными ASCII-файлами, разделенными на секции, начинающиеся с имени секции, заключенного в квадратные скобки. Далее следует список параметров в виде параметр=значение, каждый параметр размещается в отдельной строке. В этот файл можно вставлять комментарии -- строки начинающиеся с `;'.

Пример взят из файла WORKSHOP.INI:

[User Controls]
BorShade=E:\BORLANDC\WORKSHOP\BWCC.DLL

[RWS_Bitmap]
PercentLeft=50
ZoomLeft=1
ZoomRight=1
bVert=0

[RWS_Font]
PercentLeft=50
ZoomLeft=4
ZoomRight=1
bVert=1

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

int GetProfileInt(lpszSection, lpszEntry, nDefault);
int GetProfileString(
lpszSection, lpszEntry, lpszDefault, lpsBuffer, nMaxBuffer
);
BOOL WriteProfileString(lpszSection, lpszEntry, lpszString);

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

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

Функция WriteProfileString позволяет не только записывать параметры, но и удалять, для чего надо указать lpszString равным NULL. Можно удалить целиком всю секцию, указав для этого lpszEntry равным NULL.

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

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

int GetPrivateProfileInt( lpszSection, lpszEntry, nDefault, lpszIniFile );
int GetPrivateProfileString(
lpszSection, lpszEntry, lpszDefault, lpsBuffer, nMaxBuffer, lpszIniFile
);
BOOL WritePrivateProfileString( lpszSection, lpszEntry, lpszString, lpszIniFile );

Последний параметр этих функций lpszIniFile задает имя файла настройки. Если вы не указываете путь к файлу, то он размещается в каталоге Windows.

Реестр Windows

RegOpenKey Opens a specified key

RegCreateKey Creates a specified key

RegCloseKey Closes a key and releases the key's handle

RegQueryValue Retrieves the text string for a specified key

RegSetValue Associates a text string with a specified key

RegDeleteKey Deletes a specified key

RegEnumKey Enumerates the subkeys of a specified key

#include shellapi.h

LONG RegOpenKey(hkey, lpszSubKey, lphkResult);
HKEY hkey; /* handle of an open key */
LPCSTR lpszSubKey; /* address of string for subkey to open */
HKEY FAR* lphkResult; /* address of handle of open key */

The RegOpenKey function opens the specified key.

Parameter Description

hkey Identifies an open key (which can be HKEY_CLASSES_ROOT). The key opened by the RegOpenKey function is a subkey of the key identified by this parameter. This value should not be NULL.

lpszSubKey Points to a null-terminated string specifying the name of the subkey to open.

lphkResult Points to the handle of the key that is opened.

Returns

The return value is ERROR_SUCCESS if the function is successful. Otherwise, it is an error value.

Comments

Unlike the RegCreateKey function, the RegOpenKey function does not create the specified key if the key does not exist in the database.

Example

char szBuff[80];
LONG cb;
HKEY hkStdFileEditing;

if (
RegOpenKey(
HKEY_CLASSES_ROOT,
"NewAppDocument\\protocol\\StdFileEditing",
&hkStdFileEditing
) == ERROR_SUCCESS
) {

cb = sizeof(szBuff);
if (
RegQueryValue(
hkStdFileEditing,
"handler",
szBuff,
&cb
) == ERROR_SUCCESS
&& lstrcmpi("nwappobj.dll", szBuff) == 0
) RegDeleteKey(hkStdFileEditing, "handler");
RegCloseKey(hkStdFileEditing);
}

Страницы: 1, 2, 3, 4


© 2010 BANKS OF РЕФЕРАТ