Операционная система Microsoft Windows 3.1 для программиста -том 3

         

Файл winhook/kbhook.rc


/* Таблица перекодировки */

XlatTableCaps XLAT BEGIN '00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F' '10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F' '20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F' '25 21 2D 2F 22 3A 2C 2E AD 3F 3A 3B 3C 3D 3E 3F' '40 D4 C8 D1 C2 D3 C0 CF D0 D8 CE CB C4 DC D2 D9' 'C7 C9 CA DB C5 C3 CC D6 D7 CD DF 5B 5C 5D 5E 5F' '60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F' '70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F' '80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F' '90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F' 'A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF' 'B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 C6 BB C1 BD DE A1' 'C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF' 'D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA D5 DC DA DD DF' 'E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF' 'F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF'

END

XlatTable XLAT BEGIN '00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F' '10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F' '20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F' '30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F' '40 F4 E8 F1 E2 F3 E0 EF F0 F8 EE EB E4 FC F2 F9' 'E7 E9 EA FB E5 E3 EC F6 F7 ED FF 5B 5C 5D 5E 5F' '60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F' '70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F' '80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F' '90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F' 'A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF' 'B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 E6 BB E1 BD FE BF' 'C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF' 'D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA F5 DC FA FD DF' 'E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF' 'F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF'

END

Файл определения модуля DLL-библиотеки приведен в листинге 3.14.



Файл winhook/kbhook.def


; ============================= ; Файл определения модуля ; ============================= LIBRARY KBHOOK DESCRIPTION 'DLL-библиотека KBHOOK, (C) 1994, Frolov A.V.' EXETYPE windows CODE preload fixed DATA preload moveable single HEAPSIZE 1024 EXPORTS SetKbHook @10 RemoveKbHook @11 KbHookProc @12 MsgHookProc @13

С помощью этого файла экспортируются функции SetKbHook и RemoveKbHook, предназначенные, соответственно, для установки и удаления фильтров, а также функции фильтров KbHookProc и MsgHookProc.



Файл isshare/isshare.cpp


// ===================================================== // Приложение определяет, загружена ли утилита MS-DOS // share.exe, и выводит соответствующее сообщение // =====================================================

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

int ShareLoaded(void);

#pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { int rc;

// Проверяем, загружена ли share.exe rc = ShareLoaded();

if (!rc) { // Если код возврата равен -1, функция не смогла // создать на диске временный файл, необходимый // для выполнения проверки if(rc == -1) { MessageBox(NULL, "File creation error", "", MB_OK);
}

// Если код возврата равен 0, share.exe не загружена else { MessageBox(NULL, "Share NOT loaded", "", MB_OK);
} }



// Если код возврата равен 1, share.exe загружена else { MessageBox(NULL, "Share loaded", "SHARE Test", MB_OK);
}

return 0; }

// ------------------------------------------------------- // Функция ShareLoaded // Проверяет, загружена ли утилита share.exe // -------------------------------------------------------

int ShareLoaded(void) { HFILE hfTempFile; OFSTRUCT ofs; char szBuf[144]; union REGS regs; int rc;

// Создаем временный файл на диске GetTempFileName(0, "tst", 0, szBuf);
hfTempFile = _lcreat(szBuf, 0);

// Если файл создать не удалось, возвращаем -1 if (hfTempFile == HFILE_ERROR) { return(-1);
}

// Пытаемся заблокировать первый байт созданного файла

regs.x.bx = hfTempFile; // идентификатор файла regs.h.ah = 0x5c; // код функции MS-DOS regs.h.al = 0; // код операции блокирования regs.x.cx = 0; // CX:DX - смещение в файле regs.x.dx = 0; regs.x.si = 0; // SI:DI - длина блокируемой области regs.x.di = 1;

// Вызываем функцию MS-DOS intdos(&regs, &regs);

// Если установлен флаг переноса, выполнение блокирования // невозможно. Считаем, что в этом случае // утилита share.exe не загружена if(regs.x.cflag == 1) { rc = 0; }




// Если блокирование прошло успешно, // разблокируем и удаляем временный файл else { regs.x.bx = hfTempFile; regs.h.ah = 0x5c; regs.h.al = 1; // код операции разблокирования regs.x.cx = 0; regs.x.dx = 0; regs.x.si = 0; regs.x.di = 1; intdos(&regs, &regs);
rc = 1; }

// Закрываем временный файл _lclose(hfTempFile);

// Удаляем временный файл OpenFile(szBuf, &ofs, OF_DELETE);

return rc; }

Функция WinMain проверяет, загружена ли утилита share.exe, вызывая функцию ShareLoaded, определенную в приложении.

Эта функция может вернуть 0, 1 или -1. Если функция вернула 0, share.exe не загружена. Если функция вернула -1, произошла ошибка при создании временного файла. И, наконец, если функция ShareLoaded вернула 1, share.exe загружена.

Для создания временного файла используется функции GetTempFileName и _lcreat. Первая из этих двух функций получает имя временного файла, вторая - создает и открывает временный файл.

Для выполнения блокировки приложение вызывает функцию MS-DOS, пользуясь известной вам функцией intdos.

Если утилита share.exe установлена и блокировка файла выполнена успешно, функция ShareLoaded разблокирует файл, вызывая функцию MS-DOS с кодом 0x5c еще раз, но с другим значением регистра AL.

В любом случае перед возвратом из функции временный файл закрывается функцией _lclose и затем удаляется функцией OpenFile.

Файл определения модуля для приложения ISSHARE приведен в листинге 4.2.


Файл isshare/isshare.def


; ============================= ; Файл определения модуля ; ============================= NAME ISSHARE DESCRIPTION 'Приложение ISSHARE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 4096 CODE preload moveable discardable DATA preload moveable multiple



Файл oem3ansi/oem3ansi.cpp


// ---------------------------------------- // Перекодировка текстового файла // из OEM в ANSI с использованием // дополнительной таблицы перекодировки // ----------------------------------------

#define STRICT #include <windows.h>
#include <commdlg.h>
#include <mem.h>

// Прототипы функций HFILE GetSrcFile(void);
HFILE GetDstFile(void);
int Oem3Ansi(HFILE, HFILE);

// Указатель на таблицу перекодировки, // которая будет загружена из ресурсов char far * lpXlatTable;

// Идентификатор копии приложения HINSTANCE hInst;

// ------------------------------- // Функция WinMain // -------------------------------

#pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Идентификаторы файлов HFILE hfSrc, hfDst;

// Положение ресурса в файле HRSRC hResource;

// Идентификатор таблицы перекодировки HGLOBAL hXlatTable;

// Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance;

// Определяем расположение ресурса hResource = FindResource(hInstance, "XlatTable", "XLAT");

// Получаем идентификатор ресурса hXlatTable = LoadResource(hInstance, hResource);

// Фиксируем ресурс в памяти, получая его адрес lpXlatTable = (char far *)LockResource(hXlatTable);

// Если адрес равен NULL, при загрузке или // фиксации ресурса произошла ошибка if(lpXlatTable == NULL) { MessageBox(NULL, "Resource loading error", NULL, MB_OK);
return(-1);
}

// Открываем входной файл. hfSrc = GetSrcFile();
if(!hfSrc) return 0;

// Открываем выходной файл hfDst = GetDstFile();
if(!hfDst) return 0;

// Выполняем перекодировку файла if(Oem3Ansi(hfSrc, hfDst)) { MessageBox(NULL, "Low Memory", NULL, MB_OK);
}

// Закрываем входной и выходной файлы _lclose(hfSrc);
_lclose(hfDst);

// Разблокируем и освобождаем ресурс UnlockResource(hXlatTable);
FreeResource(hXlatTable);

return 0; }

// ------------------------------- // Функция GetSrcFile // Выбор файла для перекодировки // -------------------------------




HFILE GetSrcFile(void) { OPENFILENAME ofn;

char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; HFILE hf;

szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME));

// Инициализируем нужные нам поля

// Добавляем флаг OFN_ENABLETEMPLATE, который // разрешает использование шаблона ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE;

// Идентификатор модуля, содержащего шаблон // диалоговой панели. В нашем случае это // идентификатор копии приложения ofn.hInstance = hInst;

// Имя ресурса, содержащего шаблон ofn.lpTemplateName = (LPSTR)"Open";

// Заполняем остальные поля ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = NULL;

// Выбираем входной файл if (GetOpenFileName(&ofn)) { // Открываем на чтение hf = _lopen(ofn.lpstrFile, OF_READ);
return hf; } else return 0; }

// ------------------------------- // Функция GetDstFile // Выбор файла для записи // результата перекодировки // -------------------------------

HFILE GetDstFile(void) { OPENFILENAME ofn;

char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";

HFILE hf;

szFile[0] = '\0';

memset(&ofn, 0, sizeof(OPENFILENAME));

// Добавляем флаг OFN_ENABLETEMPLATE, который // разрешает использование шаблона ofn.Flags = OFN_HIDEREADONLY | OFN_ENABLETEMPLATE;

// Идентификатор модуля, содержащего шаблон ofn.hInstance = hInst;

// Имя ресурса, содержащего шаблон ofn.lpTemplateName = (LPSTR)"Open";

// Изменяем заголовок диалоговой панели ofn.lpstrTitle = (LPSTR)"Выберите выходной файл";

// Заполняем остальные поля ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = NULL;



// Выбираем выходной файл if (GetSaveFileName(&ofn)) {

// Открываем на запись. // При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0);
return hf; } else return 0; }

// ------------------------------- // Функция Oem3Ansi // Перекодировка файла // -------------------------------

int Oem3Ansi(HFILE hfSrcFile, HFILE hfDstFile) { // Счетчик прочитанных байт DWORD cbRead;

// Размер файла DWORD dwFileSize;

// Идентификатор глобального блока // памяти, который будет использован для // чтения файла HGLOBAL hglbBuf;

// Указатель на глобальный блок памяти unsigned char huge * hBuf;

// Определяем размер файла. Для этого // устанавливаем текущую позицию на // конец файла dwFileSize = _llseek(hfSrcFile, 0l, 2);

// Устанавливаем текущую позицию // на начало файла _llseek(hfSrcFile, 0l, 0);

// Заказываем глобальный блок памяти, // размер которого равен длине файла hglbBuf = GlobalAlloc(GMEM_FIXED, dwFileSize);
hBuf = (unsigned char huge *)GlobalLock(hglbBuf);

// Если мало свободной памяти, // возвращаем код ошибки if(hBuf == NULL) return(-1);

// Читаем файл в полученный блок памяти cbRead = _hread(hfSrcFile, hBuf, dwFileSize);

// Выполняем перекодировку for(long i=0; i < cbRead; i++) { // Перекодировка по таблице, // загруженной из ресурсов hBuf[i] = lpXlatTable[hBuf[i]];

// Перекодировка из OEM в ANSI OemToAnsiBuff((const char far*)&hBuf[i], (char far*)&hBuf[i], 1);
}

// Сохраняем содержимое блока памяти в // выходном файле _hwrite(hfDstFile, hBuf, dwFileSize);

// Расфиксируем и освобождаем // блок памяти GlobalUnlock(hglbBuf);
GlobalFree(hglbBuf);
return 0; }

Функция WinMain сохраняет идентификатор копии приложения в глобальной переменной hInst. Этот идентификатор потребуется впоследствии для загрузки шаблона диалоговой панели из ресурсов приложения.

Далее, так же как и первой версии приложения OEM3ANSI, функция WinMain загружает из ресурсов дополнительную таблицу перекодировки, сохраняя ее адрес в переменной lpXlatTable.

Затем функция WinMain открывает входной и выходной файлы, вызывая функции GetSrcFile и GetDstFile, определенные в нашем приложении.


Эти функции выбирают файлы с использованием шаблона, созданного нами на основе стандартного шаблона диалоговой панели "Open" и функций GetOpenFileName, GetSaveFileName.

После перекодировки выбранного файла (которая выполняется функцией Oem3Ansi), файлы закрываются, ресурс, содержащий таблицу перекодировки, расфиксируется и освобождается.

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

Перед тем, как вызвать функцию GetOpenFileName, предназначенную для выбора входного файла, мы должны подготовить соответствующим образом структуру ofn типа OPENFILENAME. В частности, для обеспечения возможности работы с шаблоном в поле Flags структуры ofn необходимо указать флаг OFN_ENABLETEMPLATE. В поле hInstance этой же структуры необходимо записать идентификатор копии приложения, ресурсы которого содержат шаблон, а в поле lpTemplateName нужно записать указатель на строку имени ресурса:

ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLETEMPLATE; ofn.hInstance = hInst; ofn.lpTemplateName = (LPSTR)"Open";

Функция GetDstFile инициализирует структуру ofn аналогичным образом. Единственное отличие заключается в том, что в поле lpstrTitle записывается адрес строки, содержащей заголовок "Выберите выходной файл":

ofn.Flags = OFN_HIDEREADONLY | OFN_ENABLETEMPLATE; ofn.hInstance = hInst; ofn.lpTemplateName = (LPSTR)"Open"; ofn.lpstrTitle = (LPSTR)"Выберите выходной файл";

Для перекодирования файла в приложении OEM3ANSI определена функция с именем Oem3Ansi.

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

dwFileSize = _llseek(hfSrcFile, 0l, 2);

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



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

_llseek(hfSrcFile, 0l, 0);

После определения размера входного файла функция Oem3Ansi заказывает фиксированный блок из глобальной области памяти (при помощи функции GlobalAlloc), и фиксирует его для получения адреса (при помощи функции GlobalLock). Адрес блока записывается в переменную hBuf типа unsigned char huge*. Размер заказанного блока равен размеру файла.

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

cbRead = _hread(hfSrcFile, hBuf, dwFileSize);

Перекодировка выполняется в следующем цикле:

for(long i=0; i < cbRead; i++) { hBuf[i] = lpXlatTable[hBuf[i]]; OemToAnsiBuff((const char far*)&hBuf[i], (char far*)&hBuf[i], 1);
}

Для перекодировки по дополнительной таблице мы адресуемся к блоку памяти через указатель типа huge, так как размер блока памяти может превышать 64 Кбайт.

Функция OemToAnsiBuff может перекодировать буфер размером не более 64 Кбайт, поэтому мы вызываем ее отдельно для каждого байта перекодируемого буфера.

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

_hwrite(hfDstFile, hBuf, dwFileSize);

Перед возвратом управления функция Oem3Ansi расфиксирует и освобождает заказанный ранее блок памяти, вызывая функции GlobalUnlock и GlobalFree.

Файл описания ресурсов приложения OEM3ANSI приведен в листинге 4.4.


Файл oem3ansi/oem3ansi.rc


/* Таблица перекодировки */

XlatTable XLAT xlatcyr.tbl

/* Шаблон диалоговой панели*/

Open DIALOG 23, 21, 264, 134 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Выберите входной файл" FONT 8, "Helv" BEGIN LTEXT "&Файл:", 1090, 6, 6, 76, 9, WS_CHILD | WS_VISIBLE | WS_GROUP

CONTROL "", 1152, "EDIT", ES_LEFT | ES_AUTOHSCROLL | ES_OEMCONVERT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 6, 16, 90, 12

CONTROL "", 1120, "LISTBOX", LBS_STANDARD | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_DISABLENOSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 6, 32, 90, 68

LTEXT "&Каталог:", -1, 110, 6, 92, 9, WS_CHILD | WS_VISIBLE | WS_GROUP

CONTROL "", 1088, "STATIC", SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_GROUP, 110, 18, 92, 9

CONTROL "", 1121, "LISTBOX", LBS_STANDARD | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_DISABLENOSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 110, 32, 92, 68

LTEXT "&Типы файлов:", 1089, 6, 104, 90, 9, WS_CHILD | WS_VISIBLE | WS_GROUP

CONTROL "", 1136, "COMBOBOX", CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 6, 114, 90, 36

LTEXT "&Дисковые устройства:", 1091, 110, 104, 92, 9, WS_CHILD | WS_VISIBLE | WS_GROUP

CONTROL "", 1137, "COMBOBOX", CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_AUTOHSCROLL | CBS_SORT | CBS_HASSTRINGS | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | WS_TABSTOP, 110, 114, 92, 68

CONTROL "OK", 1, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 6, 50, 14

CONTROL "Cancel", 2, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 24, 50, 14

CONTROL "&Help", 1038, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 46, 50, 14

CONTROL "&Чтение", 1040, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 208, 68, 50, 12 END

В этом файле описаны два ресурса: дополнительная таблица перекодировки XlatTable и шаблон диалоговой панели Open.

Файл определения модуля для приложения OEM3ANSI приведен в листинге 4.5.



Файл oem3ansi/oem3ansi.def


; ============================= ; Файл определения модуля ; ============================= NAME OEM3ANSI DESCRIPTION 'Приложение OEM3ANSI, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple



Литература


Charles Petzold. Programming Windows 3.1. Microsoft Press. One Microsoft Way. Redmont, Washington, 1992

Brent Rector. Developing Windows 3.1 Applications with Microsoft C/C++. SAMS, 1992

Daniel A. Norton. Writing Windows Device Drivers. Addison-Wesley Publishing Company, 1992

В. Фролов, Г. В. Фролов. Библиотека системного программиста. Том 6. Защищенный режим процессоров Intel 80286/80386/80486. Москва, "Диалог-МИФИ", 1993

Windows. Справочник для программистов. Версия 3.0. Часть 1, 2. Москва, "Научный центр", 1991

Эллис, Б. Строуструп. Справочное руководство по языку программирования C++ с комментариями. Москва, МИР, 1992



Локальная динамическая память


Для каждого приложения Windows создается автоматический сегмент данных размером 64 Кбайт, в котором располагаются статические данные , стек и локальная область данных (local heap ). Кроме этого, автоматический сегмент данных имеет заголовок размером 16 байт (рис. 2.10).

Рис. 2.10. Автоматический сегмент данных приложения Windows

Размер стека определяется оператором STACKSIZE в файле определения модуля:

STACKSIZE 8120

Минимальный размер стека, назначаемый Windows для приложений, составляет 5 Кбайт. Следует отметить, что в руководстве к SDK нет точного описания способа определения минимально необходимого объема стека. В этом руководстве предлагается определить этот объем экспериментально, причем подчеркивается, что результаты переполнения стека непредсказуемы.

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

HEAPSIZE 1024

Вы можете указать любое отличное от нуля значение.

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



LpfhHook


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



LpstrCustomFilter


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

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

Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов.

Если поле lpstrFilter содержит NULL, используется фильтр lpstrCustomFilter.



LpstrDefExt


Указатель на буфер, который содержит расширение имени файла, используемое по умолчанию. Это расширение добавляется к имени выбранного файла, если при выборе расширение имени не было указано.



LpstrFile


Поле lpstrFile должно содержать адрес текстовой строки, в которую будет записан полный путь к выбранному файлу.

Если по указанному выше адресу перед вызовом функции GetOpenFileName или GetSaveFileName расположить текстовую строку, содержащую путь к файлу, этот путь будет выбран по умолчанию сразу после отображения диалоговой панели "Open" или "Save As...".



LpstrFileTitle


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



LpstrFilter


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

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

Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов.



LpstrInitialDir


Поле lpstrInitialDir позволяет указать начальный каталог, который будет выбран для поиска файла сразу после отображения диалоговой панели "Open". Для того чтобы начать поиск в текущем каталоге, в это поле следует записать значение NULL.



LpstrTitle


С помощью этого поля можно определить заголовок диалоговой панели, появляющейся при вызове функции. Если это поле содержит NULL, будут использованы стандартные заголовки "Open" и "Save As...".



LpTemplatename


Идентификатор ресурса, содержащего шаблон диалоговой панели, используемого вместо имеющегося в DLL-библиотеке commdlg.dll. Для ссылки на ресурс можно использовать макрокоманду MAKEINTRESOURCE. Для использования альтернативного шаблона (и, соответственно, данного поля), в поле Flags следует установить флаг OFN_ENABLETEMPLATE.



LStructSize


Поле lStructSize перед вызовом функций должно содержать размер структуры OPENFILENAME в байтах.



Меню


1.1.

1.2.

1.3.

1.4.

1.5.

1.6.

1.7.

1.8.

1.9.

1.10.

1.11.

1.12.

1.13.

1.14.

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

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

Создавая меню в программах MS-DOS, вы были вынуждены либо приобретать специальные библиотеки функций или другие средства (C Tools, Turbo Vision, Vitamin C и т. п.), либо создавать свои функции для работы с меню.

Для того чтобы создать меню в приложении Windows, вам достаточно разработать его внешний вид и создать шаблон при помощи редактора Resource Workshop или аналогичного средства графического проектирования элементов пользовательского интерфейса. Шаблон меню следует записать в ресурсы приложения, после чего за работу меню отвечает операционная система Windows. Когда вы выбираете строку из меню, ваше приложение получает сообщение WM_COMMAND. Это сообщение содержит идентификатор выбранной строки.

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

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



Модели памяти


Для приложений Windows вы можете выбрать одну из четырех моделей памяти: small, medium, compact или large.

Все приложения, рассмотренные нами ранее, были подготовлены в модели памяти small. Для этой модели при загрузке приложения в память создается два сегмента - сегмент кода и автоматический сегмент данных. Перед тем как передать управление приложению, Windows записывает адрес сегмента кода в регистр CS, адрес автоматического сегмента данных - в регистры DS и SS. Таким образом, в этой модели памяти для стека и автоматического сегмента данных используется один и тот же сегмент.

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

CODE preload moveable discardable DATA preload moveable multiple

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

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

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

Вы можете также использовать модели памяти compact (один сегмент кода и несколько сегментов данных) и large (несколько сегментов кода и несколько сегментов данных). Но для этих моделей есть одно существенное ограничение - можно запускать только одну копию приложения, созданного с использованием таких моделей памяти.


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

Если ваше приложение создано в модели памяти medium, имеет смысл сгруппировать различные функции в несколько сегментов и для каждого сегмента определить свои атрибуты. Например, функции инициализации приложения следует расположить в сегменте с атрибутами PRELOAD и DISCARDABLE. В этом случае эти функции будут загружены в памяти в процессе запуска приложения и впоследствии будут удалены. Функции, обрабатывающие сообщения, должны быть загружены в память при инициализации приложения и находиться там постоянно, поэтому для них подойдет атрибут PRELOAD. Те функции, которые требуются эпизодически, можно загружать при необходимости и удалять после использования, поэтому для них следует указать атрибуты LOADONCALL и DISCARDABLE.

Для назначения атрибутов сегментам приложения файл определения модуля должен содержать оператор SEGMENTS:

CODE preload moveable discardable DATA preload moveable multiple SEGMENTS CODESEG1 moveable discardable CODESEG2 preload CODESEG3 loadoncall discardable

Для изменения имени сегмента кода в системах разработки Borland C++ версии 3.1 и Borland C++ for Windows версии 4.01 можно использовать параметр командной строки -zCname, где name - новое имя сегмента кода. По умолчанию сегмент кода имеет имя _TEXT. Для того чтобы восстановить имя сегмента кода, можно использовать параметр -zC*.

Параметры командной строки могут быть указаны непосредственно в исходном тексте приложения с помощью ключевого слова #pragma options :

#pragma options -zCCODESEG1 // Тело функции ............... #pragma options -zC*

Если вы создаете приложения с помощью Microsoft C++ версии 7.0 или Microsoft Visual C++, исходные тексты всех функций, которые должны находиться в одном сегменте, следует расположить в одном файле.Для изменения имени сегмента следует воспользоваться параметром /NT:

cl /u /c /As /Gsw /Oas /Zpe /NT CODESEG2 wndproc.c


NFileExtension


После возврата из функции это поле содержит смещение первого символа расширения имени файла относительно начала буфера lpstrFile.



NFileOffset


После возврата из функции это поле содержит смещение первого символа имени файла относительно начала буфера lpstrFile.



NFilterIndex


Поле nFilterIndex определяет номер пары строк, используемой для фильтра, указанного в поле lpstrFilter. Если в качестве значения для этого поля указать 0, будет использован фильтр, определенный в поле lpstrCustomFilter.



NMaxCustFilter


Определяет размер буфера в байтах, указанного в поле lpstrCustomFilter. Размер этого буфера должен быть не меньше 40 байт.



NMaxFile


Поле nMaxFile должно содержать размер в байтах буфера, расположенного по адресу, указанному в поле lpstrFile.

Размер этого буфера должен быть достаточным для записи полного пути к файлу. Файловая система MS-DOS допускает использование для указания пути к файлу не более 128 символов.



NMaxFileTitle


Поле nMaxFileTitle должно содержать размер указанного выше буфера.



Новый вариант приложения OEM3ANSI


Приведем еще один вариант приложения OEM3ANSI, описанного в предыдущем томе "Библиотеки системного программиста". В новом варианте приложение заказывает в глобальной области памяти буфер для чтения перекодируемого файла, причем размер этого буфера равен... размеру файла! Далее с помощью одного вызова функции _hread весь файл переписывается в буфер и перекодируется "по месту". Результат перекодировки из буфера переписывается в выходной файл за один вызов функции _hwrite. Таким образом, в нашем приложении нет привычного цикла чтения файла, который прерывается при достижении конца файла, а также нет цикла записи в файл. Механизм виртуальной памяти расширенного режима операционной системы Windows и функции _hread, _hwrite облегчают работу с файлами.

Кроме демонстрации работы перечисленных выше двух функций в новом варианте приложения OEM3ANSI для выбора входного и выходного файла мы использовали вместе со стандартными функциями GetOpenFileName и GetSaveFileName нестандартные шаблоны диалоговых панелей "Open" и "Save as...". Внешний вид диалоговой панели для выбора входного файла представлен на рис. 4.3, для выбора выходного файла - на рис. 4.4.

Рис. 4.3. Выбор входного файла

Рис. 4.4. Выбор выходного файла

Механизм шаблонов, предусмотренный во всех функциях стандартных диалоговых панелей из DLL-библиотеки commdlg.dll, можно использовать в тех случаях, когда вас не устраивает внешний вид стандартных диалоговых панелей. Например, все надписи в стандартной диалоговой панели "Open" выполнены на английском языке. Вы, вероятно, сумеете найти другой вариант библиотеки commdlg.dll (например, русифицированный), но лучше создать свой шаблон диалоговой панели, в котором вы можете перевести надписи на любой язык или внести другие изменения.

Как создать свой шаблон для стандартной диалоговой панели ?

Для этого проще всего воспользоваться приложением Borland Resource Workshop. С помощью этого приложения вы сможете "вытянуть" из DLL-библиотеки commdlg.dll описание любого имеющегося там ресурса, записав его в текстовый файл с расширением имени rc.




Запустите приложение Borland Resource Workshop. С помощью строки "Open Project" меню "File" загрузите DLL-библиотеку commdlg.dll. Вариант этой библиотеки, который вы можете распространять вместе с созданными вами приложениями, как правило, поставляется в составе системы разработки приложений. Кроме того, эта библиотека есть в системном каталоге операционной системы Windows версии 3.1.

После загрузки библиотеки в окне "commdlg.dll" вы увидите список ресурсов (рис. 4.5).



Рис. 4.5. Шаблоны диалоговых панелей и другие ресурсы в DLL-библиотеке commdlg.dll

Из ресурсов типа DIALOG выберите шаблон 1536. На экране появится окно "DIALOG:1536", в котором вы сможете редактировать стандартную диалоговую панель "Open" (рис. 4.6).



Рис. 4.6. Редактирование стандартной диалоговой панели "Open"

Однако перед тем как редактировать шаблон, его лучше сохранить в отдельном файле. Для этого из меню "Resource" выберите строку "Save resource as..." и сохраните описание шаблона, например, в файле с именем open.rc.

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

Учтите, что при редактировании шаблона не следует удалять из него органы управления, даже если по логике работы приложения они не нужны. Дело в том, что функции DLL-библиотеки commdlg.dll могут обращаться к ним. Например, они могут инициализировать списки. Поэтому, если вам надо сделать какой-либо стандартный орган управления невидимым, вы можете указать для него расположение вне видимой части экрана.

Теперь перейдем к описанию приложения OEM3ANSI. Исходные тексты главного файла приложения приведены в листинге 4.3.


Обработка прерываний в защищенном режиме


В реальном режиме для обработки прерываний используется таблица векторов прерываний, расположенная в первом килобайте адресного пространства. Эта таблица состоит из 256 элементов размером 4 байта, которые содержат полный адрес обработчиков прерывания в формате <сегмент:смещение>.

Как вы знаете, существуют аппаратные и программные прерывания. Аппаратные прерывания вырабатываются периферийными устройствами, как правило, при завершении ими операции ввода/вывода. Эти прерывания являются асинхронными по отношению к запущенным программам. Программные прерывания вызываются командой INT. Программные прерывания являются синхронными, так как они инициируются самой программой.

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

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

Механизм обработки прерываний в защищенном режиме намного сложнее. Для определения адресов обработчиков прерываний в защищенном режиме используется дескрипторная таблица прерываний IDT (Interrupt Descriptor Table ), расположение которой определяется содержимым специального системного регистра. Эта таблица содержит дескрипторы специальных типов - вентили прерываний, вентили исключений и вентили задач.

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

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

Приложение Windows не должно пытаться изменить дескрипторную таблицу прерываний. Не следует также думать, что эта таблица расположена по адресу 0000h:0000h, селектор 0000h вообще не используется для адресации памяти.

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



Описание таблицы акселераторов


Таблица акселераторов определяется в файле описания ресурсов приложения в следующем виде:

<Id> ACCELERATORS BEGIN ....... ....... ....... END

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

Между операторами BEGIN и END располагаются строки описания акселераторов. Они имеют следующий формат (в квадратных скобках указаны необязательные параметры):

Key, AccId, [KeyType[,]] [NOINVERT] [ALT] [SHIFT] [CONTROL]

Поле Key определяет клавишу, которая будет использована для создания акселератора. Вы можете использовать символ в коде ASCII, заключенный в двойные кавычки (например, "F"), комбинацию символа ASCII со знаком ^ (например, "^S", что соответствует комбинации клавиш <Control+S>), ASCII-код клавиши в виде целого значения, или виртуальный код клавиши (в символьном или цифровом виде).

Поле AccId соответствует значению параметра wParam сообщения WM_COMMAND, которое попадет в функцию окна при использовании акселератора.

Поле KeyTab может принимать значения ASCII или VIRTKEY. В первом случае поле Key определяет клавишу с использованием кода ASCII, во втором - с использованием кода виртуальной клавиши. По умолчанию используется значение ASCII.

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

Если поле KeyTab содержит значение VIRTKEY, можно указывать параметры ALT, SHIFT или CONTROL. В этом случае для акселератора используется комбинация клавиши, указанной параметром Key, и клавиши ALT, SHIFT или CONTROL, соответственно.

Приведем пример описания таблицы акселераторов из приложения SMARTPAD:

APP_ACCELERATORS ACCELERATORS BEGIN "N", CM_FILENEW, VIRTKEY, CONTROL "S", CM_FILESAVE, VIRTKEY, CONTROL "O", CM_FILEOPEN, VIRTKEY, CONTROL "Z", CM_EDITUNDO, VIRTKEY, CONTROL "X", CM_EDITCUT, VIRTKEY, CONTROL "C", CM_EDITCOPY, VIRTKEY, CONTROL "V", CM_EDITPASTE, VIRTKEY, CONTROL VK_DELETE, CM_EDITCLEAR, VIRTKEY, CONTROL VK_F1, CM_HELPINDEX, VIRTKEY END

Здесь описана таблица акселераторов APP_ACCELERATORS, в которой определены девять акселераторов, т. е. девять комбинаций клавиш ускоренного выбора.

Для того чтобы акселератор, состоящий из комбинации символьной клавиши (такой, как "N") и клавиши <Control>, работал вне зависимости от состояния клавиши <Caps Lock>, мы использовали виртуальные коды. Если бы мы использовали коды ASCII, наш акселератор активизировался бы только при использовании заглавных букв (мы могли бы указать строчные буквы, например, "n", в этом случае для активизации акселератора следовало бы использовать строчные буквы).

Из-за того что клавиша <Caps Lock> может находиться в любом состоянии, лучше работать с виртуальными кодами клавиш, не зависящих от того, являются буквы строчными или прописными.

Напомним, что коды виртуальных клавиш описаны в файле windows.h.



Определение характеристик локального блока памяти


Для определения характеристик локального блока памяти предназначена функция LocalFlags , аналогичная рассмотренной нами ранее функции GlobalFlags:

UINT WINAPI LocalFlags(HLOCAL hloc);

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

Если установлен флаг LMEM_DISCARDABLE, проверяемый блок памяти может быть удален Windows в процессе дефрагментации свободной области памяти. Если же установлен флаг LMEM_DISCARDED, удаление блока памяти уже произошло.



Определение идентификатора блока памяти по его адресу


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

DWORD WINAPI GlobalHandle(UINT uGlobalSel);

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

Младшее слово возвращаемого значения содержит идентификатор блока памяти, старшее - селектор блока памяти. В случае ошибки возвращается нулевое значение.

В файле windowsx.h определена макрокоманда GlobalPtrHandle , упрощающая получение идентификатора блока памяти по его логическому адресу:

#define GlobalPtrHandle(lp) \ ((HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(lp))))

Макрокоманда SELECTOROF определена в файле windows.h и предназначена для получения селекторной компоненты логического адреса:

#define SELECTOROF(lp) HIWORD(lp)

В файле windows.h есть также определения для макрокоманды OFFSETOF , возвращающей компоненту смещения, и макрокоманда MAKELP , конструирующая указатель из компонент смещения и селектора:

#define OFFSETOF(lp) LOWORD(lp) #define MAKELP(sel, off) ((void FAR*)MAKELONG((off), (sel))) #define MAKELONG (low, high) ((LONG)(((WORD)(low)) | \ (((DWORD)((WORD)(high))) << 16)))

Если вы работаете с транслятором Borland C++ for Windows, вместо этих макрокоманд можете использовать знакомые вам макрокоманды FP_SEG , FP_OFF и MK_FP , описанные в файле dos.h:

#define FP_SEG(fp)((unsigned)(void _seg*)(void far*)(fp)) #define FP_OFF(fp)((unsigned)(fp)) #define MK_FP(seg,ofs)((void _seg*)(seg)+(void near*)(ofs))



Определение размера блока памяти


С помощью функции GlobalSize вы можете определить размер блока памяти по его идентификатору:

DWORD WINAPI GlobalSize(HGLOBAL hglb);

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



Определение типа устройства ввода/вывода


Иногда приложению требуется определить тип и расположение используемого дискового устройства ввода/вывода. Для этого можно воспользоваться функцией GetDriveType :

UINT WINAPI GetDriveType(int DriveNumber);

Параметр DriveNumber определяет номер диска, для которого требуется определить тип и расположение (0 соответствует устройству A:, 1 - B:, и т. д.).

Функция может вернуть 0 при ошибке или одно из следующих значений:

Значение Описание
DRIVE_REMOVABLE Сменный диск
DRIVE_FIXED Несменный диск
DRIVE_REMOTE Удаленный диск, расположен на другой машине в сети



Орган управления TOOLBAR


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

На рис. 1.18 показан Toolbar, расположенный под главным меню приложения Microsoft Word for Windows версии 2.0.

Рис. 1.18. Орган управления Toolbar в текстовом процессоре Microsoft Word for Windows версии 2.0

Toolbar с точки зрения пользователя представляет собой ни что иное, как набор кнопок с нарисованными на их поверхности пиктограммами. Каждая такая кнопка соответствует определенной строке в том или ином временном меню приложения. Например, самая левая кнопка на рис. 1.18 соответствует строке "New" из меню "File". Однако с кнопкой может быть связана и такая функция, для которой нет соответствия в меню приложения.

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

Вы можете сделать Toolbar из стандартных кнопок, однако обычно используются кнопки, которые рисует родительское окно (т. е. имеющие стиль BS_OWNERDRAW).

В приложении SMARTPAD мы создали Toolbar как дочернее окно с расположенными на его поверхности кнопками стиля BS_OWNERDRAW. Когда пользователь нажимает на одну из кнопок, расположенных в окне Toolbar, в функцию родительского окна, создавшего Toolbar, приходит сообщение WM_COMMAND. Параметр wParam этого сообщения однозначно соответствует расположению кнопки в окне Toolbar.

Наш Toolbar создан как класс в терминах языка программирования C++. Вы можете изменять его, приспосабливая для ваших нужд, или определять на его основе новые классы. Например, можно сделать Toolbar, расположенный в окне приложения по вертикали, или создать для Toolbar отдельное перекрывающееся окно.



Особенности работы с файлами в мультизадачной среде


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



Особенности защищенного режима работы процессора


В томе 6 "Библиотеки системного программиста", который называется "Защищенный режим процессоров Intel 80286/80386/80486", мы подробно рассмотрели работу процессора 80286 в защищенном режиме и особенности адресации памяти процессорами 80386 и 80486. Там же приведены примеры программ, переключающие процессор в защищенный режим работы и выполняющие в этом режиме обращение к аппаратуре компьютера. Однако, учитывая, что в вашем распоряжении может не оказаться указанного выше тома, мы приведем самые необходимые сведения о защищенном режиме в этой главе.



Освобождение глобального блока памяти


Для освобождения глобального блока памяти, полученного от функции GlobalAlloc, вы должны использовать функцию GlobalFree :

HGLOBAL WINAPI GlobalFree(HGLOBAL hglb);

Идентификатор освобождаемого блока передается функции в качестве ее единственного параметра.

Функция возвращает NULL при успешном завершении или значение hglb при ошибке.

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

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

#define GlobalFreePtr(lp) \ (GlobalUnlockPtr(lp),(BOOL)GlobalFree(GlobalPtrHandle(lp)))

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



Освобождение локального блока памяти


Для освобождения локального блока памяти, полученного от функции LocalAlloc, вы должны использовать функцию LocalFree :

HLOCAL WINAPI LocalFree(HLOCAL hloc);

Идентификатор освобождаемого блока передается функции в качестве ее единственного параметра.

Функция возвращает NULL при успешном завершении или значение hloc при ошибке.

Перед освобождением зафиксированных блоков памяти их следует предварительно расфиксировать.



Открытие файлов


Для открытия файлов вы можете воспользоваться универсальной функцией OpenFile или более простой (но и более ограниченной) функцией _lopen.

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

HFILE WINAPI OpenFile( LPCSTR lpszFileName, // путь к файлу OFSTRUCT FAR* lpOpenStruct, // адрес структуры OFSTRUCT UINT fuMode); // режим работы и атрибуты

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

Параметр lpszFileName является указателем на текстовую строку в кодировке ANSI, содержащую путь к файлу и закрытую двоичным нулем. В имени файла не допускается указывать символы шаблона, такие как "*" и "?".

Через параметр lpOpenStruct передается адрес структуры OFSTRUCT, которая заполняется информацией при первом открытии файла.

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

Константа Описание
OF_READ Файл открывается только для чтения
OF_WRITE Файл открывается только для записи
OF_READWRITE Файл открывается для чтения и записи
OF_SHARE_COMPAT Открытие файла в режиме совместимости. В этом режиме несколько приложений могут одновременно открыть файл, причем все эти приложения должны открывать файл в режиме совместимости
OF_SHARE_EXCLUSIVE Файл открывается в монопольном режиме. Для всех других приложений доступ к этому файлу на чтение и запись запрещен
OF_SHARE_DENY_WRITE После открытия файла к нему запрещается доступ со стороны других приложений на запись
OF_SHARE_DENY_READ После открытия файла к нему запрещается доступ со стороны других приложений на чтение
OF_SHARE_DENY_NONE Для открываемого файла не запрещается доступ к файлу ни на чтение, ни на запись
OF_PARSE Если указан этот флаг, функция OpenFile не выполняет никаких других действий, кроме заполнения структуры OFSTRUCT
OF_DELETE Уничтожение существующего файла
OF_VERIFY Если указан этот флаг, функция OpenFile сравнивает время и дату, записанную в структуре OFSTRUCT с временем и датой изменений указанного файла. Если обнаружено несоответствие, функция OpenFile возвращает значение HFILE_ERROR
OF_SEARCH Операционная система Windows выполняет поиск файла в каталогах даже в том случае, когда текстовая строка, указанная параметром lpszFileName, содержит полный путь к файлу
OF_PROMPT Если указан этот флаг, то в случае невозможности найти указанный файл Windows выдает диалоговую панель с предложением вставить в дисковод A: дискету с файлом. Этот флаг используется очень редко
OF_CANCEL Флаг OF_CANCEL используется в сочетании с флагом OF_PROMPT. Если он указан, то в описанную выше диалоговую панель будет добавлена кнопка "Cancel", позволяющая отменить открытие файла. Приложение получит в этом случае код ошибки, соответствующий ненайденному файлу, а пользователь - возможность выйти из безвыходного состояния, в которое он может попасть, не имея под рукой дискеты с нужным файлом
OF_CREATE Выполняется создание нового файла. Если указанный файл существует, он обрезается до нулевой длины
OF_EXIST При указании этого флага функция OpenFile вначале открывает файл, а затем сразу же его закрывает. Эта бесполезная на первый взгляд операция может быть использована для того чтобы убедиться в существовании указанного файла на диске
OF_REOPEN Этот флаг используется при повторном открытии файла на основе информации, хранящейся в структуре OFSTRUCT
<


Когда функция OpenFile вызывается в первый раз для открытия файла, она заполняет структуру OFSTRUCT , описанную в файле windows.h следующим образом:

typedef struct tagOFSTRUCT { BYTE cBytes; BYTE fFixedDisk; UINT nErrCode; BYTE reserved[4]; char szPathName[128]; } OFSTRUCT;

Поле cBytes содержит размер самой структуры OFSTRUCT в байтах.

С помощью поля fFixedDisk приложение может определить, находится ли открытый файл на жестком диске или на флоппи-диске. если содержимое этого поля отлично от нуля, для хранения файла используется жесткий диск.

Если при открытии файла произошла ошибка, в поле nErrCode записывается код ошибки. Возможные значения для кода ошибки приведены в приложении 1.

Поле reserved зарезервировано и не должно использоваться.

В поле szPathName находится полный путь к файлу в кодировке OEM.

Если функция OpenFile показалась вам слишком сложной в использовании, в ряде случаев для открытия файла вы сможете ограничиться функцией _lopen :

HFILE WINAPI _lopen(LPCSTR lpszFileName, int fnOpenMode);

Функция возвращает идентификатор открытого файла или HFILE_ERROR при ошибке.

Параметр lpszFileName, так же как и для функции OpenFile, является указателем на текстовую строку в кодировке ANSI, содержащую путь к файлу и закрытую двоичным нулем. В имени файла не допускается указывать символы шаблона, такие как "*" и "?".

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

Константа Описание
READ Файл открывается только для чтения
WRITE Файл открывается только для записи
READWRITE Файл открывается для чтения и записи
OF_SHARE_COMPAT Открытие файла в режиме совместимости. В этом режиме несколько приложений могут одновременно открыть файл, причем все эти приложения должны открывать файл в режиме совместимости
OF_SHARE_EXCLUSIVE Файл открывается в монопольном режиме. Для всех других приложений доступ к этому файлу на чтение и запись запрещен
OF_SHARE_DENY_WRITE После открытия файла к нему запрещается доступ со стороны других приложений на запись
OF_SHARE_DENY_READ После открытия файла к нему запрещается доступ со стороны других приложений на чтение
OF_SHARE_DENY_NONE Для открываемого файла не запрещается доступ к файлу ни на чтение, ни на запись
<


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

Функция GetWindowsDirectory позволяет определить расположение каталога, в который была установлена операционная система Windows:

UINT WINAPI GetWindowsDirectory( LPSTR lpSysPath, // адрес буфера UINT cbSysPath); // размер буфера

Параметр lpSysPath является указателем на буфер размером не менее 144 байт, в который будет записан путь к искомому каталогу.

С помощью параметра cbSysPath необходимо указать размер буфера в байтах.

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

Для определения пути к системному каталогу Windows предназначена функция GetSystemDirectory :

UINT WINAPI GetSystemDirectory( LPSTR lpSysPath, // адрес буфера UINT cbSysPath); // размер буфера

Назначение параметров этой функции аналогично назначению параметров функции GetWindowsDirectory.

Так как системный каталог Windows может находиться на сервере, приложение не должно пытаться создавать или изменять файлы в этом каталоге. Как правило, пользователь не имеет права записи в системный каталог Windows.


Отмена фильтра


Фильтр, установленный функцией SetWindowsHookEx, можно отменить или удалить при помощи функции UnhookWindowsHookEx :

BOOL WINAPI UnhookWindowsHookEx(HHOOK hHook);

В качестве единственного параметра этой функции следует передать идентификатор функции фильтра, полученный от функции SetWindowsHookEx.

Если отмена фильтра выполнена успешно, функция UnhookWindowsHookEx возвращает значение TRUE. В случае возникновения ошибки возвращается FALSE.



Отметка строк


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

BOOL WINAPI CheckMenuItem(HMENU hmenu, UINT idItem, UINT fuCheck);

Параметр hmenu указывает идентификатор меню, над элементом которого будет выполняться операция включения или выключения отметки.

Параметр idItem определяет элемент меню, над которым выполняется операция. Интерпретация этого параметра зависит от значения параметра fuCheck.

Параметр fuCheck может принимать значения MF_CHECKED или MF_UNCHECKED в комбинации с одним из значений: MF_BYCOMMAND или MF_BYPOSITION.

Для включения отметки элемента меню необходимо использовать значение MF_CHECKED. Для выключения отметки элемента меню укажите значение MF_UNCHECKED.

Если в параметре fuCheck указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор элемента меню, отметка которого будет изменена. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер элемента меню, отметка которого будет изменена.



Память в различных режимах работы Windows


В зависимости от режима работы (стандартный или расширенный) операционная система Windows использует то или иное распределение памяти. В любом случае процессор работает в защищенном режиме.

Операционная система Windows версии 3.0 могла работать и в реальном режиме (для чего ее надо было запускать с параметром /r), однако версия 3.1 этот режим больше не поддерживает.

Сетевой вариант операционной системы Windows - Windows for Workgroups версии 3.11 работает только в расширенном режиме.



Плавающее меню


При необходимости ваше приложение может создать временное плавающее меню , расположенное в любом месте экрана (рис. 1.7).

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

if(msg == WM_RBUTTONDOWN) { HMENU hmenuPopup; POINT pt;

pt = MAKEPOINT(lParam); ClientToScreen(hwnd, &pt);

hmenuPopup = CreatePopupMenu();

AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILENEW, "&New"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEOPEN, "&Open"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILESAVE, "&Save"); AppendMenu(hmenuPopup, MF_SEPARATOR, 0, 0); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEEXIT, "E&xit");

TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL);

DestroyMenu(hmenuPopup); }

Обработчик сообщения WM_RBUTTONDOWN, которое приходит, если вы нажимаете правую клавишу мыши, прежде всего преобразует координаты курсора мыши в экранные. Для этого он вызывает функцию ClientToScreen .

Далее при помощи функции CreatePopupMenu создается пустое временное меню. Это меню наполняется обычным образом с помощью функции AppendMenu, но оно не привязывается к главному меню приложения или какому-либо другому меню. Вместо этого создается плавающее меню. Для этого идентификатор созданного и наполненного временного меню передается функции TrackPopupMenu :

TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL);

Эта функция выводит на экран плавающее меню и создает свой собственный цикл обработки сообщений, завершающий работу после выбора строки. Поэтому функция TrackPopupMenu не возвращает управление до тех пор, пока работа с меню не будет завершена либо выбором строки, либо отказом от выбора.

После этого созданное временное меню уничтожается:

DestroyMenu(hmenuPopup);

Приведем прототип функции TrackPopupMenu :




BOOL WINAPI TrackPopupMenu( HMENU hmenu, UINT fuFlags, int x, int y, int nReserved, HWND hwnd, const RECT FAR* lprc);

Параметр hmenu должен содержать идентификатор отображаемого временного меню. Вы можете создать новое меню при помощи функции CreatePopupMenu или получить идентификатор существующего временного меню, вызвав функцию GetSubMenu.

Параметр fuFlags определяет расположение плавающего меню и клавиши мыши, с помощью которых должен выполняться выбор.

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

Флаг Описание
TPM_CENTERALIGN Центровка относительно координаты, заданной параметром x
TPM_LEFTALIGN Выравнивание по левой границе относительно координаты, заданной параметром x
TPM_RIGHTALIGN Выравнивание по правой границе относительно координаты, заданной параметром x
Дополнительно к перечисленным выше флагам вы можете указать один из двух флагов, определяющий клавишу мыши, предназначенную для выбора строки из плавающего меню:

Флаг Описание
TPM_LEFTBUTTON Левая клавиша мыши
TPM_RIGHTBUTTON Правая клавиша мыши
Параметр nReserved зарезервирован, для совместимости со следующими версиями операционной системы Windows его значение должно быть равно 0.

Параметр hwnd задает идентификатор окна, которое получит сообщение WM_COMMAND после того как пользователь сделает выбор из плавающего меню. В операционной системе Windows версии 3.1 это сообщение попадает в функцию указанного окна после того как функция TrackPopupMenu возвратит управление. В версии 3.0 сообщение WM_COMMAND попадало в функцию окна до возврата управления функцией TrackPopupMenu.

Параметр lprc является указателем на структуру типа RECT, определяющую координаты прямоугольной области, в которой пользователь может выполнять выбор из меню. Если сделать щелчок мышью за пределами этой области, плавающее меню исчезнет с экрана. Такие действия эквивалентны отказу от выбора. Если задать для этого параметра значение NULL, размеры и расположение указанной выше прямоугольной области будут совпадать с размерами плавающего меню.


Подключение меню к окну приложения


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



Подключение меню при регистрации класса окна


Если при регистрации класса окна в поле lpszMenuName структуры типа WNDCLASS указать адрес текстовой строки, содержащей имя шаблона меню в файле ресурсов, все перекрывающиеся и временные окна, создаваемые на базе этого класса, будут иметь меню, определенное данным шаблоном. Дочерние окна (child window) не могут иметь меню.

Например, пусть в файле описания ресурсов шаблон меню определен под именем APP_MENU:

APP_MENU MENU BEGIN .... .... .... END

В этом случае для подключения меню при регистрации класса вы должны записать адрес текстовой строки "APP_MENU" в поле lpszMenuName структуры wc, имеющей тип WNDCLASS:

wc.lpszMenuName = "APP_MENU";

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

Например, пусть в файле описания ресурсов и в файле исходного текста приложения определена константа:

#define APP_MENU 123

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

wc.lpszMenuName = MAKEINTRESOURCE(APP_MENU);

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

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



Подключение меню при создании окна


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

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

hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // размеры и расположение окна CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна hmenu, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

Значение параметра идентификатора меню может быть получено, например, от функции LoadMenu , определенной в программном интерфейсе Windows:

HMENU WINAPI LoadMenu(HINSTANCE hInstance, LPCSTR lpszMenuName);

Параметр hInstance должен содержать идентификатор текущей копии приложения, полученный через соответствующий параметр функции WinMain.

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

Функция LoadMenu возвращает идентификатор загруженного меню или NULL при ошибке.

Перед завершением своей работы приложение должно уничтожить загруженное меню функцией DestroyMenu :

BOOL WINAPI DestroyMenu(HMENU hmenu);

В качестве единственного параметра функции DestroyMenu необходимо указать идентификатор уничтожаемого меню.

Функция DestroyMenu возвращает в случае успеха значение TRUE, при ошибке - FALSE.



Получение глобального блока памяти


Для получения глобального блока памяти вы должны использовать функцию GlobalAlloc :

HGLOBAL WINAPI GlobalAlloc(UINT fuAlloc, DWORD cbAlloc);

Параметр fuAlloc определяет тип выделяемой памяти. Размер блока памяти в байтах должен передаваться через параметр cbAlloc, причем вы можете заказать блок памяти размером больше, чем 64 Кбайт. Для стандартного режима работы Windows можно заказать блок памяти размером до 1 Мбайт без 80 байт, для расширенного - до 16 Мбайт без 64 Кбайт.

Функция возвращает идентификатор глобального блока памяти или NULL, если Windows не может выделить память указанного объема.

Параметра fuAlloc должен быть указан как логическая комбинация следующих значений:

Флаг Описание
GMEM_DDESHARE Блок памяти будет использоваться совместно несколькими приложениями при помощи механизма динамического обмена данными DDE
GMEM_DISCARDABLE Заказывается удаляемый блок памяти. Этот флаг должен использоваться совместно с флагом GMEM_MOVEABLE
GMEM_FIXED Заказывается фиксированный блок памяти. Этот флаг несовместим с флагом GMEM_MOVEABLE.При работе в среде Windows версии 3.1 в защищенном режиме фиксированный сегмент, созданный с использованием флага GMEM_FIXED, является перемещаемым. Для такого сегмента в процессе перемещения логический адрес не изменяется, но линейный (и, следовательно, физический) может изменяться
GMEM_LOWER Синоним для GMEM_NOT_BANKED. Не используется в Windows версии 3.1
GMEM_MOVEABLE Заказывается перемещаемый блок памяти. Логический адрес перемещаемого блока памяти может изменяться. Этот флаг несовместим с флагом GMEM_FIXED
GMEM_NOCOMPACT Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один и удалять блоки памяти, отмеченные как удаляемые
GMEM_NODISCARD Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один
GMEM_NOT_BANKED Получить блок памяти вне фрейма дополнительной памяти EMS. Не используется в Windows версии 3.1
GMEM_NOTIFY Если заказанный объект будет удален, требуется вызов процедуры извещения. Процедура извещения назначается функцией GlobalNotify и должна располагаться в фиксированном сегменте кода в библиотеке DLL. С ее помощью приложение может разрешить или запретить Windows удалять блок данных
GMEM_SHARE Синоним для GMEM_DDESHARE
GMEM_ZEROINIT Во все байты блока необходимо записать нулевые значения
GHDN Синоним для комбинации флагов GMEM_MOVEABLE и GMEM_ZEROINIT
GPTR Синоним для комбинации флагов GMEM_FIXED и GMEM_ZEROINIT

Идентификатор, полученный от функции GlobalAlloc, нельзя использовать для адресации памяти непосредственно. Напомним, что пока вы не зафиксировали блок памяти, его логический адрес недоступен.

Приведем для примера фрагмент кода, в котором мы получаем перемещаемый блок памяти размером 200000 байт, причем во все байты полученного блока записываются нулевые значения:

hmemGlobal = GlobalAlloc(GHND, 200000l);

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

hmemGlobalDisc = GlobalAlloc( GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l);



Получение информации


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