Отправляет email-рассылки с помощью сервиса Sendsay

Borland C++ Builder - всякая всячина

  Все выпуски  

Borland C++ Builder - всякая всячина (№18. Особенности разработки консольных приложений)


Служба Рассылок Subscribe.Ru

Приветствую всех получателей рассылки Borland C++ Builder - всякая всячина!

№18. Особенности разработки консольных приложений (Вопрос 2 - встраивание ресурсов в exe-файл)

"...
Шариковский рот тронула едва заметная сатирическая улыбка, и он разлил водку по рюмкам.
- Вот все у вас как на параде, - заговорил он, - салфетку - туда, галстук - сюда, да "извините", да "пожалуйста-мерси", а так, чтобы по-настоящему, - это нет. Мучаете сами себя, как при царском режиме.
- А как это "по-настоящему"? - Позвольте осведомиться.
Шариков на это ничего не ответил Филиппу Филипповичу, а поднял рюмку и произнес:
- Ну желаю, чтобы все...
- И вам также, - с некоторой иронией отозвался Борменталь.
..."
(М.А. Булгаков "Собачье сердце")

 

Вот и пролетел веселый Новый год, кончился мой долгожданный отпуск, я вернулся, посвежевщий, круглый, как колобок, готовый к труду и обороне, чего и вам всем всячески желаю.Надеюсь, в наступившем году смогу регулярно уделять время рассылке, делать более качественные выпуски, расширить многократно аудиторию моих читателей. Только недавно обратил внимание, что страница рассылки по указанному адресу http://www.homeline.ru/vasco/subscribe.html недоступна. Как оказалось, это было связано с глюком с псевдонимами у моего провайдера. Сейчас ситуация исправлена, в будущем постараюсь регулярно проверять работоспособность адреса.

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

Итак, в прошлом выпуске я рассказал о том, как поднять или понизить приоритет консольного приложения. Сегодня мы рассмотрим вопрос внедрения в ваше приложение произвольного рисунка, например, рисунок JPG, который будет являться заставкой приложения. Можно было бы, конечно, воспользоваться готовыми компонентами, но мы-то создаем консольное приложение, требовательное к размеру конечного файла, и потому легких путей не ищем. Также проблематично включение рисунка в дистрибутив в виде отдельного файла, так как это также нарушит требования компактности. Поэтому лучшим вариантом будет встроить рисунок в exe-файл в виде ресурса, а во время выполнения "распаковать" его во временный файл с расширением .jpg . Кстати, используя данный метод, очень удобно создавать простенькие самораспаковывающиеся дистрибутивы, если в виде ресурсов в exe-файл включить все необходимые для работы приложения файлы проекта.

Для начала покопайтесь в своих закромах (Диск С) и отыщите какой-нибудь небольшой рисунок (лучше .gif или .jpg), такой, чтобы его расширение "поняла" ваша программа просмотра картинок (например, ACDSee). "Положите" его в папку с проектом (для еще более ленивых, чем ваш покорный слуга, я выложил на странице рассылки архив выпуска, где такой рисунок уже имеет место быть (да простят меня коммунисты за него). Кроме того, в каком-нибудь блокноте создайте какой-нибудь файл с расширением .rc (например, "file.rc"). В нем должна присутствовать одна строчка:

Image RCDATA file.gif

где "Image" определяет название ресурса, "RCDATA" - пользовательский тип ресурса, а "file.gif" - название файла рисунка.

Примечание: в конце строки в .rc-файле не забудте нажать "Enter", Билдер иногда не может распознать "незавершенные" строки.

После этого вы должны будете "подцепить" этот файл с описанием ресурса к вашему проекту с помощью главного меню Project->Add to Project.... Если в открывшемся окне диалога вы не найдете файла file.rc, выберите в списке "Тип файлов:" пункт Resource file (*.rc).

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

//---------------------------------------------------------------------------
 
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HRSRC Res= FindResource(NULL, "Image", RT_RCDATA);
    if (Res!=NULL)
    {
      DWORD ResLen= SizeofResource(NULL, Res);
      HGLOBAL ResMemory= LoadResource(NULL, Res);
      HANDLE ResFile= CreateFile("tmp.gif", GENERIC_WRITE,
        FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
      if (ResFile!=INVALID_HANDLE_VALUE)
      {
        DWORD WritedBytes;
        if(WriteFile(ResFile, (void *)(ResMemory), ResLen, &WritedBytes, NULL)!=0 &&
          WritedBytes==ResLen)
        {
          CloseHandle(ResFile);
          ShellExecute(NULL, "open", "tmp.gif", NULL, NULL, SW_SHOWDEFAULT);
        }
        else CloseHandle(ResFile);
      }
    }
    return 0;
}
//---------------------------------------------------------------------------

Для начала мы проверяем существование ресурса с помощью API-функции FindResource. Первым параметром она "берет" хендл модуля, в котором ищется требуемый ресурс. Для поиска в текущем модуле (процессе) используется значение NULL. Вторым параметром указывается имя ресурса - Image. Третий параметр определяет тип ресурса - в нашем случае это константа RT_RCDATA, определяющая пользовательский тип. В случае успешного поиска мы получаем хендл искомого ресурса, с которым и будем работать в дальнейшем.

Используя полученный хендл ресурса, мы с помощью API-функции SizeofResource вычисляем размер ресурса в байтах. Первый параметр функции - хендл модуля (NULL - для текущего модуля), второй параметр - хендл ресурса.

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

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

Для работы с файлами в консольных приложениях очень удобно пользоваться API-функцией CreateFile, которая используется как для создания файлов, так и для открытия уже существующих. Она же используется и для работы с LPT- и COM-портами компьютера, о чем я постараюсь рассказать в одном из выпусков. Для своей работы она требует следующие параметры:

  • "temp.gif" - название открываемого или создаваемого файла
  • GENERIC_WRITE - открывает файл для записи (может комбинироваться с GENERIC_READ)
  • FILE_SHARE_READ - объясняет системе, что другие процессы могут одновременно только читать из этого файла
  • NULL - указатель на структуру аттрибутов безопасности (используется в ОС семейства NT), в нашем случае - не используется
  • CREATE_ALWAYS - константа поясняет системе, что она в любом случае должна предоставить нам новый файл нулевой длины (перезаписать существующий)
  • 0 - комбинация констант, определяющих аттрибуты создаваемого файла, в нашем случае все аттрибуты устанавливаются "по умолчанию"
  • NULL - хендл файла-шаблона, с которого могут быть скопированы аттрибуты (не используется в Win95)

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

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

  • ResFile - хендл файла, открытого с доступом для записи
  • (void *)(ResMemory) - хендл блока памяти, хранящего наш рисунок, "оформленный" как разименованный (void *) указатель (указатель на буфер)
  • ResLen - длина записываемого блока (у нас - длина блока памяти, содержащего рисунок)
  • &WritedBytes - адрес переменной, в которой по окончании операции будет сохранено количество записанных байт
  • NULL - указатель на структуру, описывающую параметры синхронизации (используется довольно редко - для обеспечения синхронности операций чтения и записи), в нашем случае - не используется

В случае удачной записи в файл, мы закрываем его и вызываем API-функцию ShellExecute, которая используется для запуска приложений. Также ее можно использовать, указывая в качестве имени приложения не exe-файл, а любой файл с зарегистрированным в системе расширением. В этом случае система сама определит, какое приложение использовать для открытия этого файла (например, программу просмотра рисунков ACDSee).

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

  • NULL - хендл окна, которое инициирует запуск приложения (консольное приложение не имеет своего окна, поэтому укажем NULL)
  • "open" - операция, которую необходимо проделать с файлом (можно также отправить файл на печать или открыть папку с этим файлом)
  • "tmp.gif" - имя запускаемого файла (и полный путь, при необходимости)
  • NULL - указатель на строку с параметрами запуска (если необходимо)
  • NULL - указатель на строку, определяющую рабочую папку для запускаемого приложения
  • SW_SHOWDEFAULT - вариант открытия окна запускаемого приложения (у нас - "по умолчанию")

Вот, собственно, и все. При запуске данная программа вызывает программу просмотра рисунков и та показывает рисунок, который вы "зашили" в свой exe-файл.

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



С уважением, Васильев Евгений...
Почта: vasco@homeline.ru
Сайт: http://www.homeline.ru/vasco


http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное