Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система Windows 95 для программиста

© Александр Фролов, Григорий Фролов
Том 22, М.: Диалог-МИФИ, 1993, 271 стр.

[Назад] [Содеожание] [Дальше]

1.2. Особенности программирования для Win32

В этом разделе мы собрали основные сведения, которые нужны, чтобы быстро перейти от 16-разрядного программирования в среде Windows версии 3.1 к 32-разрядному для операционных систем Microsoft Windows 95 и Microsoft Windows NT.

Для краткости далее мы будем называть 16-разрядный программный интерфейс Microsoft Windows версии 3.1 интерфейсом Win16 , а новый, 32-разрядный, - интерфейсом Win32 . Эта терминология соответствует использованной в Microsoft SDK.

Итак, начнем с самого начала - с функции WinMain.

Функция WinMain

В 11 томе "Библиотеки системного программиста", мы описывали функцию WinMain для приложений Win16 следующим образом:

int PASCAL WinMain(HINSTANCE hInstance, 
  HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);

Для приложений Win32 это описание немного изментися:

int APIENTRY WinMain(HINSTANCE hInstance, 
  HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);

На первый взгляд, изменений немного - мы заменили PASCAL на APIENTRY . Однако есть и другие, невидимые невооруженным глазом изменения.

Прежде всего, вспомним определение типа int. Разрядность переменной этого типа зависит от системы. Для DOS и Microsoft Windows версии 3.1 эта разрядность равна 16 бит, для Microsoft Windows 95 и Microsoft Windows NT - 32 бит. Если вы собираетесь составлять программу таким образом, чтобы она работала в разных системах (или даже на разных платформах), не следует делать никаких предположений относительно разрядности переменных типа int.

Что же касается типов PASCAL и pascal, то в 32-разрядной системе разработки Microsoft Visual C++ версии 2.0 они не рекомендуются для использования, хотя и определены для совместимости следующим образом:

#define PASCAL  __stdcall
#define pascal  __stdcall

Вместо этих типов вы должны указывать типы APIENTRY, WINAPI, CALLBACK (последний используется в функциях обратного вызова, например, в функции окна):

#define CALLBACK    __stdcall
#define WINAPI      __stdcall
#define APIENTRY    WINAPI

Согласно документации, ключевое слово __stdcall определяет функцию, аргументы которой помещаются в стек справа налево.

Займемся теперь параметрами функции WinMain.

Приложениям Win16 через первые два параметра передаются, соответственно, идентификатор приложения hInstance и идентификатор предыдущей копии приложения hPrevInstance, если в системе одновременно работает несколько копий одного приложения. Если же запущена только одна копия приложения, параметр hPrevInstance имеет значение NULL.

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

Приложения Win32 работают в разных адресных пространствах, поэтому они не могут воспользоваться идентификатором своей предыдущей копии, как приложения Win16. Более того, такое приложение вообще никак не сможет использовать параметр hPrevInstance , так как его значение для 32-разрядных приложений Microsoft Windows 95 и Microsoft Windows NT всегда равно NULL.

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

Другое новшество касается не только идентификаторов приложений, но и всех других идентификаторов в 32-разрядных версиях Microsoft Windows. Идентификаторы теперь стали 32-разрядными. И если раньше вы могли определять переменные для хранения идентификаторов как int, unsigned и т. п., то теперь необходимо пользоваться специально предназначенными для этого типами (HINSTANCE, HICON, HBRUSH и т. д.). Впрочем, так нужно было поступать и раньше, при создании приложений Win16.

Что же касается параметров lpCmdLine и nCmdShow, то здесь практически ничего не изменилось.

Параметр lpCmdLine по-прежнему указывает на строку параметров, а параметр nCmdShow содержит их количество. Однако теперь тип LPSTR соответствует 32-разрядному смещению переменной типа CHAR, а не дальнему указателю на эту переменную в формате <селектор:смещение>.

Параметр nCmdShow определяет способ отображения окна (минимизированное SW_MINIMIZE, нормальное SW_SHOW и т. д.). Мы уже рассказывали об использовании этого параметра в 11 томе "Библиотеки системного программиста".

Функция окна WndProc

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

LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Сравним это со старым описанием, которым мы пользовались в приложениях Win16:

LRESULT CALLBACK __export
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Обратите внимание, что в новом варианте отсутствует ключевое слово __export. В 32-разрядных приложениях необходимость в явном экспортировании функции окна отпала. Транслятор Microsoft Visual C++ версии 2.0 вообще не распознает __export как ключевое слово.

Идентификатор окна hWnd, имеющий тип HWND, стал в Microsoft Windows 95 и Microsoft Windows NT 32-разрядным, так же как и все остальные идентификаторы. Код сообщения msg, имеющий тип UINT, также стал 32-разрядным.

Однако наибольшие изменения затронули параметры wParam и lParam. Они не только оба стали 32-разрядными, но и изменили свой формат по сравнению с тем, что был в приложениях Win16.

Напомним, что в приложениях Win16 параметр wParam был 16-разрядный, а lParam - 32-разрядный. Теперь оба эти параметра стали 32-разрядными.

Казалось бы, это не должно вызвать никаких существенных изменений, однако давайте вспомним, что все идентификаторы в Win32 стали 32-разрядными. В результате изменился формат сообщений.

Обработка 32-разрядных сообщений

В приложениях Win16 параметр lParam часто использовался для передачи сразу двух значений, соответственно, через младшее и старшее слово. Для удобства выделения этих значений предназначались специальные макрокоманды LOWORD и HIWORD .

Например, для сообщения WM_COMMAND в младшем слове параметра lParam передавался 16-разрядный идентификатор дочернего окна hWnd (органа управления, пославшего это сообщение), в старшем - 16-разрядный код извещения wCmd. Через параметр wParam передавался 16-разрядный идентификатор органа управления wId (рис. 1.14).

Рис. 1.14. Формат параметров wParam и lParam для сообщения WM_COMMAND в приложениях Win16

В приложениях Win32 идентификатор окна hWnd стал 32-разрядным, поэтому его никак нельзя разместить в старшем слове параметра lParam. Теперь приходится отводить для идентификатора окна все разряды параметра lParam.

А что делать с кодом извещения wCmd, который оказался "выселен" из хорошо обжитого им параметра lParam?

Его пришлось перенести в старшее слово параметра wParam, удвоившего свою разрядность (рис. 1.15).

Рис. 1.15. Формат параметров wParam и lParam для сообщения WM_COMMAND в приложениях Win32

Таким образом, идентификатор окна остался на месте, а код извещения "переехал" в старшее слово параметра wParam. Аналогичным образом изменился формат и других сообщений.

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

Итак, первый фрагмент.

case WM_COMMAND:
{
  wId  = wParam;
  hWnd = LOWORD(lParam);
  nCmd = HIWORD(lParam);
  ...
}

Приложения Win32 должны разбирать сообщение WM_COMMAND на составные части по-другому:

case WM_COMMAND:
{
  wId  = LOWORD(wParam);
  hWnd = (HWND)(UINT)lParam;
  nCmd = HIWORD(wParam);
  ...
}

В файле windowsx.h определены макрокоманды разборки сообщений (message crackers), которые позволяют "извлечь" из сообщений отдельные параметры.

Приведем для примера определения макрокоманд разборки сообщения WM_COMMAND из файла windowsx.h:

#define GET_WM_COMMAND_ID(wp, lp)   LOWORD(wp)
#define GET_WM_COMMAND_HWND(wp, lp) (HWND)(lp)
#define GET_WM_COMMAND_CMD(wp, lp)  HIWORD(wp)
#define GET_WM_COMMAND_MPS(id, hwnd, cmd) \
  (WPARAM)MAKELONG(id, cmd), (LONG)(hwnd)

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

Предположим, нам надо организовать обработку сообщений WM_CREATE и WM_LBUTTONDOWN . Мы готовим две функции с именами WndProc_OnCreate и WndProc_OnLButtonDown, которые будут заниматься обработкой этих сообщений:

BOOL WndProc_OnCreate(HWND hWnd, 
  LPCREATESTRUCT lpCreateStruct);
void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, 
  int x, int y, UINT keyFlags);

Имена функций не играют никакой роли, однако Microsoft рекомендует составлять их из префикса имени функции окна (например, WndProc) с добавлением строки _On и названия сообщения, в котором удален префикс WM_.

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

LRESULT WINAPI 
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
     HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);
     HANDLE_MSG(hWnd, WM_LBUTTONDOWN, WndProc_OnLButtonDown);
	default:
      return(DefWindowProc(hWnd, msg, wParam, lParam));
  }
}

Теперь оператор switch будет выглядеть компактнее, так как для обработки каждого сообщения в него добавляется только одна строка.

Следует, однако, заметить, что для работы с макрокомандой HANDLE_MSG вы должны использовать в качестве имен для последних двух параметров функции WndProc имена wParam и lParam (только эти имена).

Как выглядит функция обработчика сообщения, вызываемая макрокомандой HANDLE_MSG?

Так же, как и обычная функция, за одним исключением - она должна оканчиваться вызовом макрокоманды FORWARD_<имя сообщения> в операторе return (даже если функция имеет тип void):

void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, 
  int x, int y, UINT keyFlags)
{
  ...  
  return FORWARD_WM_LBUTTONDOWN(hWnd, fDoubleClick, 
           x, y, keyFlags, DefWindowProc);
}

Но при обработке сообщения WM_CREATE вы должны в случае успешной инициализации данных окна вернуть значение TRUE, иначе окно так и не будет создано:

BOOL WndProc_OnCreate(HWND hWnd, 
  LPCREATESTRUCT lpCreateStruct)
{
  ...  
  return TRUE;
}

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

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

#pragma warning(disable: 4098)

Функции программного интерфейса Win32

Так как приложения Win32 работают с 32-разрядными идентификаторами, а также 32-разрядными координатами (графические функции), очевидно, что в Microsoft Windows 95 и Microsoft Windows NT должны были измениться многие функции программного интерфейса.

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

Если раньше в приложении Win16 вы могли вызывать функции прерывания INT 21h для выполнения вызовов DOS, теперь вам придется пользоваться соответствующим набором функций интерфейса Win32.

Так как по сравнению с Microsoft Windows версии 3.1 в операционных системах Microsoft Windows 95 и Microsoft Windows NT полностью изменились структура памяти и методы управления памятью, многие привычные вам функции больше не нужны (например, функция GlobalFix ).

[Назад] [Содеожание] [Дальше]