1. Ресурсы

Мы уже говорили, что формат загрузочного модуля приложения Windows сложнее формата загрузочного модуля программы MS-DOS. Кроме выполняемого кода и констант в загрузочном модуле приложения Windows находятся дополнительные данные - ресурсы.

Что такое ресурсы?

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

Физически ресурсы находятся внутри exe-файла приложения. Они могут загружаться в оперативную память автоматически при запуске приложения или по запросу приложения (явному или неявному). Такой механизм обеспечивает экономное использование оперативной памяти, так как все редко используемые данные можно хранить на диске и загружать в память только при необходимости.

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

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

Для создания и редактирования ресурсов предназначены специальные приложения. Мы будем использовать приложение Resource Workshop, которое входит в состав системы разработки программного обеспечения Borland C++ for Windows версии 3.1. С помощью этого средства можно создавать новые ресурсы или редактировать уже имеющиеся. В частности, можно отредактировать ресурсы, расположенные внутри загрузочного модуля приложения Windows или внутри библиотеки динамической загрузки DLL.

1.1. Включение ресурсов

Для включения ресурсов в загрузочный модуль приложения вам надо создать текстовый файл описания ресурсов. Этот файл может быть создан либо текстовым редактором (например, встроенным в Borland C++ for Windows), либо при помощи приложения Resource Workshop.

Файл описания ресурсов имеет расширение имени .rc. Его необходимо включить в проект приложения наряду с файлами исходных текстов и файлом определения модуля.

В процессе сборки загрузочного модуля файл описания ресурсов компилируется специальным компилятором ресурсов rc.exe. Компилятор ресурсов поставляется вместе с системой разработки приложений Windows. Он преобразует входной текстовый файл описания ресурсов в двоичный файл с расширением имени .res (вы можете указывать в проекте либо текстовый, либо двоичный вариант файла описания ресурсов, однако лучше использовать текстовый вариант, так как его можно редактировать). Перед запуском компилятора ресурсов система разработки приложений Windows запускает препроцессор текстового описания ресурсов rcpp.exe, который обрабатывает разделители комментариев и директивы препроцессора Си.

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

На рис. 1.1 схематически изображен процесс сборки загрузочного модуля приложения Windows.

Рис. 1.1. Сборка загрузочного модуля приложения Windows

Исходные тексты приложения Windows, составленные на языках программирования С, С++ или на языке ассемблера, компилируются в объектные модули *.obj. С помощью утилиты tlib.exe объектные модули могут быть собраны в библиотеки *.lib. Далее редактор связей, входящий в систему разработки приложений Windows, собирает из объектных модулей промежуточный вариант загрузочного модуля, не содержащий ресурсов. При этом используется файл определения модуля *.def. Файл описания ресурсов *.rc компилируется утилитой rc.exe в двоичный файл *.res. На последней стадии формирования загрузочного модуля промежуточный вариант exe-файла комбинируется с файлом ресурсов для получения окончательного варианта загрузочного модуля.

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

1.2. Таблица текстовых строк

Мы начнем изучение ресурсов с самого простого в использовании - таблицы строк. Таблица строк содержит текстовые строки, закрытые двоичным нулем, на которые можно ссылаться по идентификатору. Идентификатор представляет собой обыкновенное целое число. Вместо чисел обычно используют символические имена, определенные с помощью директивы #define.

Создание таблицы

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

STRINGTABLE [параметры загрузки][тип памяти]
BEGIN
StringID, строка
...
...
...
END

В качестве параметров загрузки можно указывать значения PRELOAD или LOADONCALL (используется по умолчанию). Ресурс с параметром загрузки LOADONCALL загружается в память при обращении к нему со стороны приложения. Ресурс типа PRELOAD загружается сразу после запуска приложения.

Тип памяти, выделяемой при загрузки ресурса, может быть FIXED или MOVABLE. Дополнительно для ресурсов типа можно указать MOVABLE тип DISCARDABLE. Если указан тип FIXED, ресурс будет находиться в памяти по постоянному адресу. Ресурс типа MOVABLE может перемещаться Windows при необходимости уплотнения памяти. Если для перемещаемого ресурса указан тип DISCARDABLE, Windows может забрать у приложения память, выделенную для ресурса. Если ресурс потребуется приложению, Windows загрузит его повторно из exe-файла приложения.

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

STRINGTABLE
BEGIN
1, "Файл %s не найден"
2, "Ошибка при записи в файл %s"
3, "Ошибка ввода/вывода"
END

Загрузка строки из таблицы

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

int WINAPI LoadString(
  HINSTANCE hInst,      // идентификатор приложения
  UINT idResource,      // идентификатор ресурса
  LPSTR lpszBuffer,     // адрес буфера
  int cbBuffer);        // размер буфера в байтах

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

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

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

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

Приложение STRING

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

Листинг 1.1. Файл string\string.cpp

// ----------------------------------------
// Работа с таблицей строк 
// ----------------------------------------

#define STRICT
#include <windows.h>
#include "string.hpp"

// Прототип функции обработки ошибки
void Error(void);

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine,
        int       nCmdShow)
{
  // Буфера для работы со строками
  BYTE szAppName[20];
  BYTE szWindowName[80];
  BYTE szMsg[80];
  BYTE szBuf[80];
  int  cb;

  // Загружаем строку с идентификатором IDS_APPNAME
  cb = LoadString(hInstance, IDS_APPNAME,
    szAppName, sizeof(szAppName));

  // Если в буфер записано 0 символов,
  // это означает, что ресурс не найден.
  // В этом случае мы вызываем функцию обработки
  // ошибки и завершаем работу приложения 
  if(!cb)
  {
    Error(); return -1;
  }

  // Загружаем строку с идентификатором IDS_WINDOWNAME
  cb = LoadString(hInstance, IDS_WINDOWNAME,
    szWindowName, sizeof(szWindowName));

  if(!cb)
  {
    Error(); return -1;
  }

  // Загружаем строку с идентификатором IDS_MESSAGE
  cb = LoadString(hInstance, IDS_MESSAGE,
    szMsg, sizeof(szMsg));

  if(!cb)
  {
    Error(); return -1;
  }

  // Подготавливаем буфер для вывода сообщения
  wsprintf(szBuf, szMsg, (LPSTR)szAppName, 1994);

  // Выводим сообщение, составленное из строк
  // и числа 1994
  MessageBox(NULL, szBuf, szWindowName,
     MB_OK | MB_ICONINFORMATION);

  return 0;
}

// --------------------------------------------
// Функция обработки ошибки загрузки ресурса
// --------------------------------------------
void Error(void)
{
  MessageBox(NULL, "Ошибка при загрузке ресурса",
    "Error", MB_OK | MB_ICONSTOP);
}

В главный файл исходного текста приложения включается include-файл string.hpp (листинг 1.2), в котором описаны символические константы для идентификации строк.

Листинг 1.2. Файл string\string.hpp

#define IDS_APPNAME    1
#define IDS_WINDOWNAME 2
#define IDS_MESSAGE    3

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

Листинг 1.3. Файл string\string.rc

#include "string.hpp"
/* Таблица строк */
STRINGTABLE
BEGIN
IDS_APPNAME,    "String"
IDS_WINDOWNAME, "String Application"
IDS_MESSAGE,    "%s Demo, (C) Frolov A.V., %d"
END

Как и для любого приложения Windows, в проект включается файл определения модуля (листинг 1.4).

Листинг 1.4. Файл string\string.def

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

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

cb = LoadString(hInstance, IDS_APPNAME,
  szAppName, sizeof(szAppName));

Обратите внимание на последнюю строку в таблице строк:

IDS_MESSAGE,    "%s Demo, (C) Frolov A.V., %d"

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

wsprintf(szBuf, szMsg, (LPSTR)szAppName, 1994);

Сообщение, которое будет выведено на экран приложением STRING, показано на рис. 1.2.

Рис. 1.2. Сообщение приложения STRING

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

Редактирование таблицы строк

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

Этот инструмент обладает широкими возможностями. С его помощью вы можете создать новые ресурсы или отредактировать имеющиеся в загрузочном файле приложения, текстовом или двоичном файле описания ресурсов. Из-за ограниченного объема книги мы не можем привести полное описание приложения Resource Workshop, однако постепенно мы расскажем вам о том, как его использовать для создания или редактирования основных типов ресурсов.

Для начала давайте попробуем использовать Resource Workshop для редактирования строк только что описанного приложения STRING.

Запустите приложение Resource Workshop. Его пиктограмма должна находиться в группе системы разработки приложений Borland C++ for Windows.

Затем из меню "File" выберите строку "Open Project...". На экране появится диалоговая панель "Open Project" (рис. 1.3).

Рис. 1.3. Диалоговая панель "Open Project"

При помощи меню "File Type" вы можете выбрать тип файла, из которого собираетесь загрузить ресурсы для редактирования. Как видно из рисунка, ресурсы могут содержаться в файлах разных типов, например, в библиотеке динамической загрузки DLL или в драйвере устройства DRV.

Так как мы собираемся изменить таблицу строк в готовом приложении Windows, выберите в меню "File Type" строку "EXE application". Затем с помощью меню "Path", "Files" и "Directories" укажите путь к загрузочному файлу приложения. В нашем случае надо выбрать файл string.exe. После этого в главном окне приложения Resource Workshop появится окно с заголовком "string.exe", в котором будут перечислены все имеющиеся в файле ресурсы (рис. 1.4).

Рис. 1.4. Ресурсы из файла string.exe

Файл string.exe содержит только один тип ресурсов - таблицу строк STRINGTABLE. Для редактирования таблицы сделайте двойной щелчок левой клавишей мыши по цифре 1, расположенной в окне "string.exe". В главном окне приложения Resource Workshop появится окно "STRINGTABLE:1" (рис. 1.5).

Рис. 1.5. Окно "STRINGTABLE:1"

В этом окне в столбце "String" вы можете отредактировать текстовые строки.

После редактирования сохраните изменения, выбрав в меню "File" строку "Save Project".

На рис. 1.6 представлен результат "локализации" приложения STRING описанным выше образом. Мы перевели сообщение и заголовок окна на русский язык, не меняя файл описания ресурсов и не выполняя повторную сборку проекта.

Рис. 1.6. Измененное приложение STRING

Однако есть одна небольшая проблема. К сожалению, Resource Workshop, который поставляется вместе с системой разработки Borland C++ for Windows версии 3.1, не понимает русские буквы "ю" и "я". При вводе строк, содержащих эти буквы, на экране появляется сообщение об ошибке (рис. 1.7).

Рис. 1.7. Сообщение об ошибке приложения Resource Workshop

Возможно, в следующих версиях Resource Workshop эта ошибка (или особенность!) будет исправлена, а пока вместо букв "ю" и "я" мы рекомендуем указывать соответствующие им восьмеричные коды. Букве "ю" соответствует код \376, букве "я" - код \377. При этом строка будет выглядеть так:

Возможно, в следу\376щих верси\377х это будет исправлено

Функции для работы с текстовыми строками

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

Функция lstrcmp сравнивает строки, заданные параметрами:

int WINAPI lstrcmp(LPCSTR lpszString1, LPCSTR lpszString2);

Функция возвращает отрицательное значение, если строка lpszString1 меньше чем строка lpszString2, положительное в противоположном случае, и равное нулю при равенстве сравниваемых строк. При сравнении учитываются особенности национального алфавита для указанной при помощи приложения Control Panel страны. Функция способна сравнивать строки с двухбайтовыми кодами символов. Учитываются также заглавные и прописные буквы. Размер сравниваемых строк не может превышать 64 Кбайт.

Функция lstrcmpi предназначена для сравнения двух строк, но без учета заглавных и прописных букв:

int WINAPI lstrcmpi(LPCSTR lpszString1, LPCSTR lpszString2);

В остальном она полностью аналогична функции lstrcmp.

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

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

LPSTR WINAPI lstrcpy(LPSTR lpszString1, LPCSTR lpszString2);

Эта функция копирует строку lpszString2 в строку lpszString1, возвращая указатель на первую строку или NULL при ошибке. В отличие от своего аналога из библиотеки функций MS-DOS (функции strcpy) эта функция способна работать со строками, содержащими двухбайтовые коды символов. Размер копируемой строки не должен превышать 64 Кбайт.

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

LPSTR WINAPI lstrcpyn(LPSTR lpszString1, LPCSTR lpszString2,
   int cChars);

Она копирует cChars символов из строки lpszString2 в строку lpszString1.

Для объединения двух строк в приложениях Windows следует применять функцию lstrcat:

LPSTR WINAPI lstrcat(LPSTR lpszString1, LPCSTR lpszString2);

Функция lstrcat добавляет строку lpszString2 к строке lpszString1. Размер строки, получившейся в результате объединения, не должен превышать 64 Кбайт. Функция возвращает указатель на строку lpszString1.

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

int WINAPI lstrlen(LPCSTR lpszString);

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

Функция IsCharAlpha возвращает значение TRUE, если символ, заданный параметром chTest, является буквой:

BOOL WINAPI IsCharAlpha(char chTest);

Функция IsCharAlphaNumeric возвращает значение TRUE, если символ, заданный параметром chTest, является буквой или цифрой:

BOOL WINAPI IsCharAlphaNumeric(char chTest);

Функция IsCharUpper возвращает значение TRUE, если символ, заданный параметром chTest, является прописным (заглавным):

BOOL WINAPI IsCharUpper(char chTest);

Функция IsCharLower возвращает значение TRUE, если символ, заданный параметром chTest, является строчным:

BOOL WINAPI IsCharLower(char chTest);

В предыдущем томе "Библиотеки системного программиста" мы рассказывали вам о том, что Windows и MS-DOS используют разные наборы символов. Приложения Windows обычно работают с наборами в стандарте ANSI, программы MS-DOS - в стандарте OEM. Там же мы описали функции, предназначенные для преобразования строк из одного стандарта в другой. Для удобства изучения материала кратко перечислим эти функции еще раз.

Для перекодировки строки символов, закрытой двоичным нулем, из набора ANSI в набор OEM предназначена функция AnsiToOem:

void WINAPI AnsiToOem(const char _huge* hpszWindowsStr,
    char _huge* hpszOemStr);

Параметр hpszWindowsStr представляет собой указатель типа _huge на преобразуемую строку, параметр hpszOemStr - указатель на буфер для записи результата преобразования.

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

void WINAPI AnsiToOemBuff(LPCSTR lpszWindowsStr,
   LPSTR lpszOemStr, UINT cbWindowsStr);

Первый параметр этой функции (lpszWindowsStr) является дальним указателем на массив, содержащий преобразуемые данные, второй (lpszOemStr) - на буфер для записи результата. Третий параметр (cbWindowsStr) определяет размер входного массива, причем нулевой размер соответствует 64 Кбайт (65536 байт).

Обратное преобразование выполняется функциями OemToAnsi и OemToAnsiBuff:

void WINAPI OemToAnsi(const char _huge* hpszOemStr, 
   char _huge* lpszWindowsStr);
void WINAPI OemToAnsiBuff(LPCSTR lpszOemStr, 
   LPSTR lpszWindowsStr, UINT cbOemStr);

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

Для преобразований символов в строчные или прописные приложение Windows должно пользоваться функциями AnsiLower, AnsiLowerBuff, AnsiUpper, AnsiUpperBuff.

Функция AnsiLower преобразует закрытую двоичным нулем текстовую строку в строчные (маленькие) буквы:

LPSTR WINAPI AnsiLower(LPSTR lpszString);

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

Функция AnsiUpper преобразует закрытую двоичным нулем текстовую строку в прописные (большие) буквы:

LPSTR WINAPI AnsiLower(LPSTR lpszString);

Параметр функции lpszString - дальний указатель на преобразуемую строку.

Функция AnsiLowerBuff позволяет преобразовать в строчные (маленькие) буквы заданное количество символов:

UINT WINAPI AnsiLowerBuff(LPSTR lpszString, UINT cbString);

Первый параметр функции (lpszString) является указателем на буфер, содержащий преобразуемые символы, второй (cbString) определяет количество преобразуемых символов (размер буфера). Нулевой размер соответствует буферу длиной 64 Кбайт (65536 байт).

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

Функция AnsiUpperBuff позволяет преобразовать в прописные (большие) буквы заданное количество символов:

UINT WINAPI AnsiUpperBuff(LPSTR lpszString, UINT cbString);

Первый параметр функции lpszString(lpszString) является указателем на буфер, содержащий преобразуемые символы, второй (cbString) определяет количество преобразуемых символов (размер буфера). Нулевой размер соответствует буферу длиной 64 Кбайт (65536 байт).

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

Функция AnsiNext возвращает новое значение для указателя, передвинутое вперед по строке на одни символ:

LPSTR WINAPI AnsiNext(LPCSTR lpchCurrentChar);

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

Функция AnsiPrev выполняет передвижение указателя в направлении к началу строки:

LPSTR WINAPI AnsiPrev(LPCSTR lpchStart, 
   LPCSTR lpchCurrentChar);

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

1.3. Пиктограмма

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

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

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

Операционная система Windows содержит ряд встроенных пиктограмм, которые доступны приложениям.

Приложения Windows активно работают с графическими изображениями, состоящими из отдельных пикселов и имеющих прямоугольную или квадратную форму. Такие изображения называются bitmap (битовый образ). Для представления цвета отдельного пиксела в изображении может быть использовано различное количество бит памяти. Например, цвет пиксела черно-белого изображения может быть представлен одним битом, для представления цвета 16-цветного изображения нужно 4 бита. Цвет одного пиксела изображения в режиме TrueColor представляется 24 битами памяти.

Можно считать, что пиктограмма является упрощенным вариантом изображения bitmap. Пиктограммы хранятся в файлах с расширением имени *.ico (хотя можно использовать любое расширение). В одном файле обычно находится несколько вариантов пиктограмм. Когда Windows рисует пиктограмму, он выбирает из файла наиболее подходящий для текущего режима работы видеоадаптера.

Создание пиктограммы

Приложение Resource Workshop позволяет вам создать пиктограммы размером 32х16 пикселов (для отображения на мониторах CGA), 32х32 пиксела (для отображения в стандартном режиме VGA) и 64х64 пиксела (для режимов SVGA с большим разрешением). Вы можете создать монохромную или цветную пиктограмму. Цветная пиктограмма содержит 8, 16 или 256 цветов (Windows версии 3.0 не умеет работать с пиктограммами, содержащими 256 цветов).

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

Для того чтобы создать файл *.ico, запустите Resource Workshop и из меню "File" выберите строку "New Project...". В появившейся диалоговой панели выберите переключатель ".ICO" и нажмите кнопку "OK". На экране появится диалоговая панель "New icon image" (рис. 1.8).

Рис. 1.8. Диалоговая панель "New icon image"

В этой диалоговой панели вы должны выбрать нужный вам размер пиктограммы (переключатель "Size") и количество цветов, используемых в пиктограмме (переключатель "Colors").

Сделав выбор, нажмите кнопку "OK". На экране появятся окна, показанные на рис. 1.9.

Рис. 1.9. Создание новой пиктограммы

Большое окно с заголовком "ICON:ICON_1 32x32 2 Colors" предназначено для редактирования графического изображения пиктограммы и имеет полосу с инструментами, аналогичными используемым в стандартном для Windows приложении Paint Brush.

Фактически для пиктограммы любого формата используется два битовых изображения. Одно изображение является маской, второе - собственно графическим изображением пиктограммы. Накладывая маску, вы можете создать пиктограмму произвольной формы (а не только прямоугольную).

При помощи левой клавиши мыши вы можете выбрать цвет для изображения (на рисунке обозначен как FG), а при помощи левой - для фона пиктограммы (на рисунке обозначен как BG). Можно выбрать слово "Transparent", в этом случае будет использоваться "прозрачный" цвет. Участки пиктограммы, нарисованные с использованием режима "Inverted" (инвертировать) будут инвертировать цвет фона окна, над которым находится пиктограмма.

Для того чтобы добавить в файл *.ico еще один вариант пиктограммы, сделайте текущим окно "ICON:ICON_1" и из меню "Images" (появится после того как указанное выше окно станет активным) выберите строку "New Image...". Выберите нужное вам разрешение и приступайте к созданию нового варианта пиктограммы. Для этого сделайте двойной щелчок левой клавишей мыши по новой строке, появившейся в окне "ICON:ICON_1".

После создания файла пиктограммы сохраните его, выбрав из меню "File" строку "Save file as...". В появившейся диалоговой панели вам нужно указать путь и имя файла, использовав расширение имени *.ico.

Включение пиктограммы в файл описания ресурсов

Для включения пиктограммы в файл описания ресурсов используется оператор ICON (для включения пиктограмм в диалоговые панели используется другой формат, который мы сейчас не будем рассматривать):

IconID ICON [параметры загрузки] [тип памяти] имя файла

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

AppIcon ICON myicon.ico

Идентификатор пиктограммы IconID можно указывать как символическое имя (см. предыдущую строку) или как целое число - идентификатор ресурса:

456 ICON great.ico

После сборки проекта файл пиктограммы будет вставлен в исполняемый файл приложения Windows.

Использование пиктограммы при регистрации класса окна

Во всех приложениях, описанных в предыдущем томе "Библиотеки системного программиста", для класса главного окна приложения мы определяли встроенную в Windows пиктограмму с идентификатором IDI_APPLICATION. Для этого мы вызывали функцию LoadIcon:

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

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

HICON WINAPI LoadIcon(HINSTANCE hInst, LPCSTR lpszIcon);

Параметр hInst является идентификатором текущей копии приложения.

Параметр lpszIcon - идентификатор ресурса или дальний указатель на строку, идентифицирующую ресурс.

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

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

Следующая строка задает для класса главного окна приложения пиктограмму, определенную в файле описания ресурсов под именем AppIcon:

wc.hIcon = LoadIcon(hInstance, "AppIcon");

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

#define MAKELP(sel, off) ((void FAR*)MAKELONG((off), (sel)))
#define MAKEINTRESOURCE(i) ((LPCSTR)MAKELP(0, (i)))

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

456 ICON great.ico

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

wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(456));

Возможен еще один вариант:

wc.hIcon = LoadIcon(hInstance, "#456");

Для Windows символ "#" означает, что значение 456 является текстовой строкой.

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

BOOL WINAPI DestroyIcon(HICON hIcon);

Функция DestroyIcon уничтожает пиктограмму, идентификатор которой задан параметром hIcon и освобождает ранее занимаемую этой пиктограммой память.

Встроенные пиктограммы

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

Пиктограмма Идентификатор Применение
IDI_APPLICATION Пиктограмма, назначаемая главному окну приложения по умолчанию
IDI_ASTERISK Используется в информирующих сообщениях
IDI_EXCLAMATION Используется в предупреждающих сообщениях
IDI_HAND Используется в критических предупреждающих сообщениях
IDI_QUESTION Означает вопрос или запрос дополнительных данных

С идентификатором IDI_APPLICATION вы уже встречались при регистрации класса окна:

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

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

Идентификаторы встроенных пиктограмм определены в файле windows.h следующим образом:

#define IDI_APPLICATION  MAKEINTRESOURCE(32512)
#define IDI_HAND         MAKEINTRESOURCE(32513)
#define IDI_QUESTION     MAKEINTRESOURCE(32514)
#define IDI_EXCLAMATION  MAKEINTRESOURCE(32515)
#define IDI_ASTERISK     MAKEINTRESOURCE(32516)

Изображение пиктограммы в окне приложения

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

BOOL WINAPI DrawIcon(HDC hDC, int x, int y, HICON hIcon);

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

Пиктограмма с идентификатором hIcon будет нарисована в точке с координатами (x,y), определяемыми параметрами x и y.

Функция DrawIcon возвращает значение TRUE при успешном завершении и FALSE при ошибке.

Приложение ICO

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

Исходный текст приложения приведен в листинге 1.5.

Листинг 1.5. Файл ico\ico.cpp

// ----------------------------------------
// Изображение пиктограмм
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>
#include "ico.hpp"

void Error(void);
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char szClassName[80];
char szWindowTitle[80];
HINSTANCE hInst;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine,
        int       nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения
  int  cb;

  hInst = hInstance;

  // Загружаем текстовые строки 
  cb = LoadString(hInstance, IDS_CLASSNAME,
    szClassName, sizeof(szClassName));

  if(!cb)
  {
    Error(); return -1;
  }

  cb = LoadString(hInstance, IDS_WINDOWTITLE,
    szWindowTitle, sizeof(szWindowTitle));

  if(!cb)
  {
    Error(); return -1;
  }

  if(!InitApp(hInstance))
      return FALSE;

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

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));

  // Загружаем пиктограмму, которая будет использована
  // для отображения главного окна в свернутом виде
  wc.hIcon = LoadIcon(hInstance, "AppIcon");

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// --------------------------------------------
// Функция обработки ошибки загрузки ресурса
// --------------------------------------------
void Error(void)
{
  MessageBox(NULL, "Ошибка при загрузке ресурса",
    "Error", MB_OK | MB_ICONSTOP);
}

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

Далее функция WinMain загружает из ресурсов приложения строки с идентификаторами IDS_CLASSNAME (имя класса для главного окна приложения) и IDS_WINDOWTITLE (заголовок главного окна приложения).

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

wc.hIcon = LoadIcon(hInstance, "AppIcon");

В главный файл приложения и файл описания ресурсов включается файл ico.hpp (листинг 1.6), в котором определены символические константы для идентификации строк таблицы STRINGTABLE.

Листинг 1.6. Файл ico\ico.hpp

#define IDS_CLASSNAME    1
#define IDS_WINDOWTITLE  2

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

Листинг 1.7. Файл ico\ico.rc

#include "ico.hpp"

/* Таблица строк */
STRINGTABLE
BEGIN
IDS_CLASSNAME,   "ICOAppClass"
IDS_WINDOWTITLE, "ICO Demo"
END

/* Пиктограмма */
AppIcon ICON ico.ico

В этом файле наряду с оператором STRINGTABLE, описывающим таблицу строк, имеется оператор ICON, ссылающийся на файл пиктограммы ico.ico (листинг 1.8).

Листинг 1.8. Файл ico\ico.ico

Основная задача функции главного окна приложения (листинг 1.9) - вывод в окно встроенных пиктограмм и пиктограммы ico.ico, загруженной из ресурсов приложения (рис. 1.10).

Рис. 1.10. Главное окно приложения ICO

Листинг 1.9. Файл ico\wndproc.cpp

// =====================================
// Функция WndProc
// =====================================

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

extern HINSTANCE hInst;

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;             // индекс контекста устройства
  PAINTSTRUCT ps;      // структура для рисования

  // Размеры пиктограммы
  static int xIcon, yIcon;

  // Идентификатор пиктограммы
  HICON hIcon;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Определяем размеры пиктограммы
      xIcon = GetSystemMetrics(SM_CXICON);
      yIcon = GetSystemMetrics(SM_CYICON);
      break;
    }

    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);

      // Загружаем и рисуем встроенные пиктограммы
      hIcon = LoadIcon(0, IDI_APPLICATION);
      DrawIcon(hdc, xIcon/2, yIcon/2, hIcon);

      hIcon = LoadIcon(0, IDI_ASTERISK);
      DrawIcon(hdc, xIcon/2 + xIcon*2, yIcon/2, hIcon);

      hIcon = LoadIcon(0, IDI_EXCLAMATION);
      DrawIcon(hdc, xIcon/2 + xIcon*4, yIcon/2, hIcon);

      hIcon = LoadIcon(0, IDI_HAND);
      DrawIcon(hdc, xIcon/2 + xIcon*6, yIcon/2, hIcon);

      hIcon = LoadIcon(0, IDI_QUESTION);
      DrawIcon(hdc, xIcon/2 + xIcon*8, yIcon/2, hIcon);

      // Загружаем и рисуем свою пиктограмму,
      // указанную в файле описания ресурсов
      hIcon = LoadIcon(hInst, "AppIcon");
      DrawIcon(hdc, xIcon/2, yIcon/2 + yIcon*2, hIcon);

      EndPaint(hwnd, &ps);
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

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

xIcon = GetSystemMetrics(SM_CXICON);
yIcon = GetSystemMetrics(SM_CYICON);

Эти размеры будут использованы для размещения пиктограмм в окне.

При обработке сообщения WM_PAINT функция окна загружает и рисует пиктограммы. Для загрузки пиктограмм используется функция LoadIcon, для рисования - DrawIcon.

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

Листинг 1.10. Файл ico\ico.def

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

1.4. Курсор мыши

В предыдущем томе "Библиотеки системного программиста" мы рассказывали вам о том, как управлять курсором мыши и как изменять его форму. В этом разделе мы вновь вернемся к этому вопросу.

Курсор мыши представляет собой ни что иное, как упрощенный вариант битового изображения (bitmap), аналогичного пиктограмме. Вы можете выбрать один из встроенных курсоров, либо создать свой собственный. Для создания своего курсора его надо нарисовать, пользуясь приложением Resource Workshop, и записать в файл с расширением имени cur. Далее на файл с курсором следует сделать ссылку в файле описания ресурсов. После этого приложение может загрузить курсор в память и использовать его либо при регистрации класса окна, либо для изменения формы курсора в произвольный момент времени.

Создание курсора

Для создания файла, содержащего курсор, запустите приложение Resource Workshop. Из меню "File" выберите строку "New project...". В появившейся диалоговой панели включите переключатель ".CUR" и нажмите кнопку "OK".

На экране появятся окна, аналогичные используемым для редактирования пиктограмм (рис. 1.11).

Рис. 1.11. Редактирование курсора

После того как курсор будет нарисован, его следует сохранить в файле с расширением имени cur. Для этого выберите из меню "File" строку "Save file as...".

Включение курсора в файл описания ресурсов

Для включения курсора в файл описания ресурсов используется оператор CURSOR, аналогичный оператору ICON:

CursorID CURSOR [параметры загрузки] [тип памяти] имя файла

В качестве параметров загрузки можно указывать значения PRELOAD или LOADONCALL (используется по умолчанию). Ресурс с параметром загрузки LOADONCALL загружается в память при обращении к нему со стороны приложения. Ресурс типа PRELOAD загружается сразу после запуска приложения.

Тип памяти, выделяемой при загрузки ресурса, может быть FIXED или MOVEABLE. Дополнительно для ресурсов типа можно указать MOVEABLE тип DISCARDABLE. Если указан тип FIXED, ресурс будет находиться в памяти по постоянному адресу. Ресурс типа MOVEABLE может перемещаться Windows при необходимости уплотнения памяти. Если для перемещаемого ресурса указан тип DISCARDABLE, Windows может забрать у приложения память, выделенную для ресурса. Если ресурс потребуется приложению, Windows загрузит его повторно из exe-файла приложения.

В качестве имени файла необходимо указать имя файла, подготовленного с помощью приложения Resource Workshop или аналогичного и содержащего курсор, например:

AppCursor CURSOR mycursor.cur

Идентификатор курсора CursorID можно указывать как символическое имя (см. предыдущую строку) или как целое число - идентификатор ресурса:

789 CURSOR custom.cur

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

Для загрузки курсора следует использовать функцию LoadCursor:

HCURSOR WINAPI LoadCursor(HINSTANCE hInst, 
      LPCSTR lpszCursor);

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

Если в файле описания ресурсов идентификатор ресурса представлен символьной строкой, адрес этой строки необходимо указать в параметре lpszCursor:

HCURSOR hCustomCursor;
hCustomCursor = LoadCursor(hInstance, "AppCursor");

Если же в качестве идентификатора ресурса-курсора использовано целое число, следует использовать макрокоманду MAKEINTRESOURCE:

HCURSOR hCustomCursor;
hCustomCursor = LoadCursor(hInstance, MAKEINTRESOURCE(789));

Встроенные курсоры

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

Курсор Идентификатор Применение
IDC_ARROW Стандартный курсор в виде стрелки
IDC_IBEAM Текстовый курсор
IDC_WAIT Курсор в виде песочных часов. Используется при выполнении длительных операций
IDC_CROSS Курсор в виде перекрестия
IDC_UPARROW Курсор в виде вертикальной стрелки
IDC_SIZE Индикация изменения размера
IDC_ICON Пустая пиктограмма
IDC_SIZENWSE Индикация изменения размера
IDC_SIZENESW Индикация изменения размера
IDC_SIZEWE Индикация изменения размера
IDC_SIZENS Индикация изменения размера

Идентификаторы встроенных курсоров описаны в файле windows.h:

#define IDC_ARROW      MAKEINTRESOURCE(32512)
#define IDC_IBEAM      MAKEINTRESOURCE(32513)
#define IDC_WAIT       MAKEINTRESOURCE(32514)
#define IDC_CROSS      MAKEINTRESOURCE(32515)
#define IDC_UPARROW    MAKEINTRESOURCE(32516)
#define IDC_SIZE       MAKEINTRESOURCE(32640)
#define IDC_ICON       MAKEINTRESOURCE(32641)
#define IDC_SIZENWSE   MAKEINTRESOURCE(32642)
#define IDC_SIZENESW   MAKEINTRESOURCE(32643)
#define IDC_SIZEWE     MAKEINTRESOURCE(32644)
#define IDC_SIZENS     MAKEINTRESOURCE(32645)

Обратите внимание, что для встроенных пиктограмм и встроенных курсоров используются идентификаторы с одинаковым значением. Например, идентификатор курсора IDC_ARROW и идентификатор пиктограммы IDI_APPLICATION определены одинаково:

#define IDC_ARROW        MAKEINTRESOURCE(32512)
#define IDI_APPLICATION  MAKEINTRESOURCE(32512)

Здесь нет никакой ошибки. Так как для загрузки курсора используется функция LoadCursor, а для загрузки пиктограммы - LoadIcon, после загрузки вы получаете идентификатор того ресурса, который вам нужен.

Изменение формы курсора

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

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

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

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

wc.hCursor = LoadCursor(hInstance,  "AppCursor");

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

HCURSOR WINAPI SetCursor(HCURSOR hcur);

Параметр hcur функции SetCursor должен указывать идентификатор нового курсора, подготовленный при помощи функции LoadCursor. Если указать параметр как NULL, изображение курсора исчезнет с экрана.

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

int WINAPI ShowCursor(BOOL fShow);

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

Для включения курсора в качестве параметра fShow функции следует передать значение TRUE, для выключения - FALSE.

Возвращаемое функцией ShowCursor значение равно новому содержимому счетчика.

Изображение курсора в окне приложения

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

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

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

Приложение CURSOR

Приложение CURSOR демонстрирует использование курсора, нарисованного при помощи приложения Resource Workshop, при регистрации класса окна. Это приложение рисует в главном окне приложения все встроенные курсоры и один курсор из файла описания ресурсов (рис. 1.12).

Рис. 1.12. Главное окно приложения CURSOR

Главный файл приложения CURSOR приведен в листинге 1.11.

Листинг 1.11. Файл cursor\cursor.cpp

// ----------------------------------------
// Изображение курсоров
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mem.h>
#include "cursor.hpp"

void Error(void);
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char szClassName[80];
char szWindowTitle[80];

HINSTANCE hInst;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine,
        int       nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения
  int  cb;

  hInst = hInstance;

  // Загружаем текстовые строки 
  cb = LoadString(hInstance, IDS_CLASSNAME,
    szClassName, sizeof(szClassName));
  if(!cb)
  {
    Error(); return -1;
  }

  cb = LoadString(hInstance, IDS_WINDOWTITLE,
    szWindowTitle, sizeof(szWindowTitle));
  if(!cb)
  {
    Error(); return -1;
  }

  if(!InitApp(hInstance))
      return FALSE;

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

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));

  // Загружаем пиктограмму, которая будет использована
  // для отображения главного окна в свернутом виде
  wc.hIcon = LoadIcon(hInstance, "AppIcon");

  // Загружаем курсор для главного окна
  wc.hCursor = LoadCursor(hInst, "AppCursor");

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszMenuName = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// --------------------------------------------
// Функция обработки ошибки загрузки ресурса
// --------------------------------------------
void Error(void)
{
  MessageBox(NULL, "Ошибка при загрузке ресурса",
    "Error", MB_OK | MB_ICONSTOP);
}

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

wc.hIcon   = LoadIcon(hInstance, "AppIcon");
wc.hCursor = LoadCursor(hInst, "AppCursor");

Идентификаторы строк определены в файле cursor.hpp (листинг 1.12).

Листинг 1.12. Файл cursor\cursor.hpp

#define IDS_CLASSNAME    1
#define IDS_WINDOWTITLE  2

Файл ресурсов (листинг 1.13) содержит таблицу текстовых строк, ссылку на файлы пиктограммы и курсора.

Листинг 1.13. Файл cursor\cursor.rc

#include "cursor.hpp"

/* Таблица строк */
STRINGTABLE
BEGIN
IDS_CLASSNAME,   "CURSORAppClass"
IDS_WINDOWTITLE, "CURSOR Demo"
END

/* Пиктограмма */
AppIcon ICON cursor.ico

/* Курсор */
AppCursor CURSOR cursor.cur

В листинге 1.14 приведено изображение пиктограммы, указанной при регистрации класса главного окна приложения CURSOR.

Листинг 1.14. Файл cursor\cursor.ico

Когда курсор мыши находится внутри главного окна приложения CURSOR, он принимает форму, представленную в листинге 1.15.

Листинг 1.15. Файл cursor\cursor.cur

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

Листинг 1.16. Файл cursor\wndproc.cpp

// =====================================
// Функция WndProc
// =====================================

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

extern HINSTANCE hInst;

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;             // индекс контекста устройства
  PAINTSTRUCT ps;      // структура для рисования

  // Размеры курсора
  static int xCursor, yCursor;

  // Идентификатор курсора
  HCURSOR hCursor;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Определяем размеры курсора
      xCursor = GetSystemMetrics(SM_CXCURSOR);
      yCursor = GetSystemMetrics(SM_CYCURSOR);
      break;
    }

    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);

      // Загружаем и рисуем встроенные курсоры
      hCursor = LoadCursor(0, IDC_ARROW);
      DrawIcon(hdc, xCursor/2, yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_IBEAM);
      DrawIcon(hdc, xCursor/2 + xCursor, yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_WAIT);
      DrawIcon(hdc,xCursor/2 + xCursor*2,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_CROSS);
      DrawIcon(hdc,xCursor/2 + xCursor*3,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_UPARROW);
      DrawIcon(hdc,xCursor/2 + xCursor*4,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_SIZE);
      DrawIcon(hdc,xCursor/2 + xCursor*5,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_ICON);
      DrawIcon(hdc,xCursor/2 + xCursor*6,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_SIZENWSE);
      DrawIcon(hdc,xCursor/2 + xCursor*7,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_SIZENESW);
      DrawIcon(hdc,xCursor/2 + xCursor*8,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_SIZEWE);
      DrawIcon(hdc,xCursor/2 + xCursor*9,yCursor/2, hCursor);

      hCursor = LoadCursor(0, IDC_SIZENS);
      DrawIcon(hdc,xCursor/2 + xCursor*10,yCursor/2,hCursor);

      // Загружаем и рисуем свой курсор,
      // указанный в файле описания ресурсов
      hCursor = LoadCursor(hInst, "AppCursor");
      DrawIcon(hdc,xCursor/2, yCursor/2 + yCursor*2,hCursor);

      EndPaint(hwnd, &ps);
      return 0;
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Обратите внимание на способ, при помощи которого функция окна рисует изображение курсора. Вначале она получает идентификатор курсора, вызывая функцию LoadCursor:

hCursor = LoadCursor(0, IDC_ARROW);

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

DrawIcon(hdc, xCursor/2, yCursor/2, hCursor);

При создании окна функции окна передается сообщение WM_CREATE, которое используется для получения размеров курсора. С этой целью обработчик сообщения вызывает рассмотренную нами ранее функцию GetSystemMetrics:

case WM_CREATE:
{
  xCursor = GetSystemMetrics(SM_CXCURSOR);
  yCursor = GetSystemMetrics(SM_CYCURSOR);
  break;
}

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

Листинг 1.17. Файл cursor\cursor.def

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

1.5. Графическое изображение типа bitmap

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

С помощью графического редактора, входящего в состав Resource Workshop, или с помощью стандартного приложения Paint Brush вы можете нарисовать прямоугольное графическое изображение типа bitmap. При этом для представления цвета одного пикселя может использоваться разное количество бит. Изображение записывается в файл с расширением имени bmp.

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

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

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

В этом разделе мы расскажем вам о том, как создать и описать изображение bitmap в файле описания ресурсов, как загрузить bitmap в память и использовать его для закрашивания внутренней области окна (client area).

Создание изображения типа bitmap

Для создания изображения bitmap запустите Resource Workshop и из меню "File" выберите строку "New Project". В появившейся диалоговой панели включите переключатель ".BMP" и нажмите кнопку "OK". На экране появится диалоговая панель "New bitmap Attributes" (рис. 1.13).

Рис. 1.13. Диалоговая панель "New bitmap Attributes"

В этой диалоговой панели вам надо выбрать ширину изображения в пикселях (поле "Width in pixels"), высоту изображения в пикселях (поле "Height in pixels"), количество цветов в изображении (2, 16 или 256). После выбора нажмите кнопку "OK". В главном окне Resource Workshop появятся окна для редактирования изображения (рис. 1.14).

Рис. 1.14. Создание изображения типа bitmap

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

Если размеры создаваемого изображения невелики, можно увеличить масштаб отображения при редактировании, выбрав из меню "View" строку "Zoom in". Для возврата к прежнему масштабу отображения выберите из этого же меню строку "Zoom out".

Для сохранения созданного изображения в файле выберите из меню "File" строку "Save file as..." и укажите имя файла.

Включение изображения bitmap в файл описания ресурсов

Для включения изображения типа bitmap в файл описания ресурсов используется такой же способ, как и для включения пиктограмм. Файл описания ресурсов должен содержать оператор BITMAP:

BitmapID BITMAP [параметры загрузки] [тип памяти] имя файла

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

AppBitmap BITMAP mybrush.bmp

Идентификатор изображения bitmap BitmapID можно указывать как символическое имя или как целое число - идентификатор ресурса.

Загрузка изображения bitmap

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

HBITMAP WINAPI LoadBitmap(HINSTANCE hInst,LPCSTR lpszBitmap);

Назначение параметров этой функции аналогично назначению параметров функций LoadIcon и LoadCursor. Параметр hInst указывает идентификатор текущей копии приложения, параметр lpszBitmap - идентификатор bitmap в файле описания ресурсов.

Функция возвращает идентификатор изображения, который следует использовать для рисования bitmap.

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

BOOL WINAPI DeleteObject(HGDIOBJ hGDIObj);

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

Создание кисти для закрашивания окна

Одно из простых применений изображений bitmap - раскрашивание фона окна. С этой целью вы можете использовать изображения размером 8х8 точек, созданные при помощи Resource Workshop.

Изображение, которое будет использовано для закрашивания внутренней области окна, должно быть определено в файле описания ресурсов при помощи оператора BITMAP.

После загрузки изображения функцией LoadBitmap приложение должно создать из этого изображения кисть, вызвав функцию CreatePatternBrush:

HBRUSH WINAPI CreatePatternBrush(HBITMAP hBmp);

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

Функция CreatePatternBrush возвращает идентификатор кисти, который можно использовать при регистрации класса окна. Значение этого идентификатора следует записать в поле hbrBackground структуры wndclass, используемой для регистрации класса окна.

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

Приложение BRUSH

Приложение BRUSH демонстрирует использование изображения типа bitmap для закрашивания внутренней области окна (рис. 1.15).

Рис. 1.15. Закрашивание окна в приложении BRUSH

Главный файл приложения BRUSH приведен в листинге 1.18.

Листинг 1.18. Файл brush\brush.cpp

// ----------------------------------------
// Использование кисти для закрашивания
// внутренней области окна
// ----------------------------------------

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

// Прототипы функций

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса окна
char const szClassName[]   = "BrushAppClass";

// Заголовок окна
char const szWindowTitle[] = "Brush Demo";

// Идентификатор изображения bitmap
HBITMAP hBmp;

// Идентификатор кисти
HBRUSH  hBrush;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Загружаем изображение bitmap из ресурсов
  hBmp   = LoadBitmap(hInstance, "#123");

  // Создаем кисть для закрашивания окна
  hBrush = CreatePatternBrush(hBmp);

  // Инициализируем приложение
  if(!InitApp(hInstance))
      return FALSE;

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

  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Запускаем цикл обработки сообщений

  while(GetMessage(&msg, 0, 0, 0))
  {
    DispatchMessage(&msg);
  }

  // Перед завершением работы приложения
  // уничтожаем созданные нами изображение и кисть
  DeleteObject(hBmp);
  DeleteObject(hBrush);

  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна

  memset(&wc, 0, sizeof(wc));

  // Закраска внутренней области окна
  wc.hbrBackground = hBrush;

  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.lpszMenuName = (LPSTR)NULL;
  wc.lpszClassName = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);

  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_DESTROY:
    {
      PostQuitMessage(0);
      return 0;
    }
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Перед регистрацией класса главного окна приложения функция WinMain загружает изображение bitmap, вызывая функцию LoadBitmap:

hBmp   = LoadBitmap(hInstance, "#123");

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

Получив идентификатор изображения, функция WinMain создает кисть, для чего она вызывает функцию CreatePatternBrush:

hBrush = CreatePatternBrush(hBmp);

При заполнении структуры для регистрации класса окна в поле hbrBackground записывается идентификатор созданной кисти:

wc.hbrBackground = hBrush;

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

DeleteObject(hBmp);
DeleteObject(hBrush);

Файл ресурсов приведен в листинге 1.19.

Листинг 1.19. Файл brush\brush.rc

/* Изображение типа bitmap */
123 BITMAP brush.bmp

В этом файле описан один ресурс - изображение типа bitmap с идентификатором 123. При загрузке ресурса используется ссылка на этот идентификатор как на строку символов, для чего в качестве второго параметра функции LoadBitmap передается строка "#123". Можно также использовать ссылку с использованием макроса MAKEINTRESOURCE:

hBmp   = LoadBitmap(hInstance, MAKEINTRESOURCE(123));

Файл brush.bmp (листинг 1.20) был создан при помощи Resource Workshop. Еще раз обратим ваше внимание на то, что размер изображения для создания кисти должен быть 8х8 точек.

Листинг 1.20. Файл brush\brush.bmp

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

Листинг 1.21. Файл brush\brush.def

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

1.6. Произвольные данные

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

Включение произвольных данных в ресурсы приложения

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

RId [тип ресурса] [параметры загрузки] [тип памяти] имя файла

RId является идентификатором ресурса. Можно использовать любое имя или число.

Для типа ресурса вы можете выбрать любое обозначение. Разумеется, не следует выбирать названия предопределенных ресурсов, такие как ICON или CURSOR.

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

Hello SOUND hello.wav

Вся работа по подготовке файла с данными выполняется программистом перед сборкой проекта.

Загрузка произвольных данных из ресурсов приложения

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

HRSRC WINAPI FindResource(HINSTANCE hInst, 
   LPCSTR lpszName, LPCSTR lpszType);

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

Параметр lpszName должен содержать адрес имени ресурса. Для загрузки произвольных данных в качестве этого параметра следует передать указатель на строку, содержащую идентификатор ресурса. В приведенном выше примере используется идентификатор "Hello".

Параметр lpszType - адрес строки, содержащий тип ресурса. Для нашего примера это должна быть строка "SOUND".

Таким образом, поиск ресурса, описанного как

Hello SOUND hello.wav

должен выполняться следующим образом:

HRSRC hRsrc;
hRsrc = FindResource(hInstance, "Hello", "SOUND");

Функции FindResource в качестве третьего параметра можно передавать идентификаторы предопределенных типов ресурсов, список которых приведен ниже (некоторые из перечисленных типов ресурсов вам пока незнакомы, мы расскажем о них позже).

Идентификатор ресурса Название ресурса
RT_ACCELERATOR Таблица акселераторов
RT_BITMAP Изображение bitmap
RT_CURSOR Курсор
RT_DIALOG Диалоговая панель
RT_FONT Шрифт
RT_FONTDIR Каталог шрифтов
RT_ICON Пиктограмма
RT_MENU Меню
RT_RCDATA Произвольные данные
RT_STRING Таблица строк

Вы можете использовать функцию FindResource для загрузки таких ресурсов, как пиктограммы или курсоры, указав ей тип ресурса, соответственно, RT_ICON или RT_CURSOR. Однако в документации к SDK сказано, что загрузку предопределенных ресурсов, таких как пиктограммы и курсоры, следует выполнять специально предназначенными для этого функциями (LoadIcon, LoadCursor и т. д.).

На втором этапе, после того как ресурс найден, его следует загрузить, вызвав функцию LoadResource:

HGLOBAL WINAPI LoadResource(HINSTANCE hinst, HRSRC hrsrc);

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

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

Третий этап заключается в фиксировании ресурса в оперативной памяти функцией LockResource:

void FAR* WINAPI LockResource(HGLOBAL hGlb);

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

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

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

BOOL WINAPI GlobalUnlock(HGLOBAL hGlb);
#define UnlockResource(h) GlobalUnlock(h)

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

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

BOOL WINAPI FreeResource(HGLOBAL hGlb);

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

Приложение OEM3ANSI

В предыдущем томе "Библиотеки системного программиста" мы привели исходные тексты приложения OEM2ANSI, преобразующего файла из кодировки OEM (принятую в MS-DOS) в кодировку ANSI (принятую в Windows). В некоторых случаях нужны дополнительные таблицы перекодировки. Например, при переносе текстовых файлов из среды операционных систем ЕС ЭВМ (IBM 370) в среду Windows иногда требуется выполнять замену сходных по начертанию букв латинского алфавита на буквы русского алфавита или наоборот, букв русского алфавита на буквы латинского алфавита. Это связано с тем, что в некоторых случаях русские тексты, подготовленные в системах ЕС ЭВМ, содержат латинские буквы. Например, вместо русской заглавной буквы "А" используется латинская заглавная буква "A". Есть шрифты, где эти буквы имеют разное начертание. Кроме того, при смешивании латинских и русских букв могут появится проблемы с сортировкой.

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

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

Главный файл приложения OEM3ANSI приведен в листинге 1.22.

Листинг 1.22. Файл oem3ansi\oem3ansi.cpp

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // Выполняем перекодировку файла
  Oem3Ansi(hfSrc, hfDst);

  // Закрываем входной и выходной файлы
  _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.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;
  ofn.Flags =   OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
                                  | OFN_HIDEREADONLY;
  // Выбираем входной файл
  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.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;
  ofn.Flags             = OFN_HIDEREADONLY;

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

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

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

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

  // Буфер для считанных данных
  BYTE bBuf[2048];

  // Читаем в цикле файл и перекодируем его,
  // записывая результат в другой файл
  do {
    // Читаем в буфер 2048 байт из входного файла
    cbRead = _lread(hfSrcFile, bBuf, 2048);

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

    // Перекодируем содержимое буфера
    // из OEM в ANSI
    OemToAnsiBuff(bBuf, bBuf, cbRead);

    // Сохраняем содержимое буфера в
    // выходном файле
    _lwrite(hfDstFile, bBuf, cbRead);

  // Завершаем цикл по концу входного файла
  } while (cbRead != 0);
}

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

hResource   = FindResource(hInstance, "XlatTable", "XLAT");
hXlatTable  = LoadResource(hInstance, hResource);

После этого ресурс фиксируется в памяти:

lpXlatTable = (char far *)LockResource(hXlatTable);

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

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

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

UnlockResource(hXlatTable);
FreeResource(hXlatTable);

Функция Oem3Ansi выполняет те же действия, что и функция Oem2Ansi из приложения OEM2ANSI, рассмотренного в предыдущем томе. Дополнительно перед перекодировкой из OEM в ANSI (которую выполняет функция OemToAnsiBuff из программного интерфейса Windows) функция Oem3Ansi в цикле перекодирует прочитанный из файла буфер, пользуясь дополнительной таблицей перекодировки, загруженной из ресурсов:

for(int i=0;i < cbRead; i++)
{
  bBuf[i] = lpXlatTable[bBuf[i]];
}
OemToAnsiBuff(bBuf, bBuf, cbRead);

Файл описания ресурсов приложения OEM3ANSI (листинг 1.23) содержит описание единственного ресурса. Тип этого ресурса мы определили сами как XLAT.

Листинг 1.23. Файл oem3ansi\oem3ansi.rc

/* Таблица перекодировки */
XlatTable XLAT xlatcyr.tbl

Файл описания ресурсов ссылается на таблицу перекодировки (листинг 1.24), длина которой составляет 256 байт.

Листинг 1.24. Файл oem3ansi\xlatcyr.tbl

В этой таблице для каждого возможного значения байта находится соответствующая замена. Байты со значениями от 0x00 до 0x40 при перекодировке остаются без изменения. Вместо заглавной латинской буквы "A" с кодом 0x41 в таблице стоит значение 0x80, соответствующее (в кодировке OEM) русской заглавной букве "А". Аналогичная замена выполняется и для других латинских букв, имеющих сходное с русскими буквами начертание.

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

Листинг 1.25. Файл 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

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

Листинг 1.26. Файл oem3ansi\tabgen.cpp

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
  FILE *tfile;

  tfile=fopen("xlat.tbl","w+b");
  if(tfile == NULL)
  {
    printf("File creation error!");
    exit(1);
  }

  for(unsigned int i=0; i < 256; i++)
    fputc((char)i, tfile);

  fclose(tfile);
  return 0;
}

Эта программа создает в текущем каталоге файл с именем xlat.tbl, состоящий из 256 байт с последовательно возрастающими от 0 до 255 значениями. При использовании такого файла приложение OEM3ANSI не выполняет никакой дополнительной перекодировки, однако вы можете отредактировать этот файл, например, при помощи редактора diskedit.exe из пакета утилит Нортона.

Содержимое таблицы перекодировки можно изменять и после сборки проекта. Для этого надо воспользоваться приложением Resource Workshop.

Запустите Resource Workshop и из меню "File" выберите строку "Open Project". В меню "File type" выберите строку "EXE application" и откройте файл oem3ansi.exe. На экране появится окно с заголовком "oem3ansi.exe", в котором находится список имеющихся в файле приложения ресурсов. Вы увидите там строку "XLATTABLE". Это идентификатор ресурса, определенный в файле описания ресурсов приложения. Сделайте по нему двойной щелчок левой клавишей мыши. Появится окно редактирования "XLAT:XLATTABLE", в котором вы сможете изменить значение отдельных байт таблицы. После редактирования сохраните изменения, выбрав из меню "File" строку "Save project" и завершите приложение Resource Workshop.

1.7. Другие типы ресурсов

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