Программирование на Visual C++

Выпуск №22 от 5 ноября 2000 г.

Здравствуйте!

Мне пришла пара писем от подписчиков, где они выразили некоторую неудовлетворенность существующей  в рассылке системой поощрения авторов лучших ответов и статей (для вновьподписавшихся: см. в архиве выпуск No. 18) Они пишут, что "прежде всего надо публиковать самые интересные вопросы."

Я лично с этим целиком и полностью согласен, и всегда фактически стараюсь так и делать, хотя понятие "интересный вопрос" достаточно размыто и каждый понимает его по-своему. Для некоторых, например, интересный вопрос – "Как связать контролы на диалоге с переменными класса?", а для других – …мм, ну, совершенно другое ;)

С другой стороны, у меня нет абсолютно никакой альтернативы для поощрения авторов, кроме как морального поощрения. Думаю мало кого увлечет обещание типа "Вы увидите свое имя в рассылке, оно навечно войдет в скрижали ее истории", и т.д. и т.п.

Так вот, к чему я клоню. РАССЫЛКЕ НУЖЕН ПОСТОЯННЫЙ СПОНСОР И РЕКЛАМОДАТЕЛЬ. Тогда станет возможно назначить материальное вознаграждение за лучший ответ и лучший материал (а, возможно, и лучший вопрос тоже!)

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

И тогда, действительно, будет возможно публиковать только САМЫЕ ИНТЕРЕСНЫЕ вопросы.

Так что дело только за вами, уважаемые рекламодатели! Хочу вам напомнить, что рассылку получают около 8500 интересующихся программированием человек.

К читателям: может, у вас есть какие-нибудь идеи или просто интересные мысли по этому поводу? Не стесняйтесь – пишите мне.

СТАТЬЯ

Сегодня я предлагаю вам заметку, написанную уже воистину постоянным автором нашей рассылки – Александром Шаргиным.

Три способа подключения DLL

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

При неявном подключении (implicit linking) линкеру передаётся имя библиотеки импорта (с расширением lib), содержащей список функций DLL, которые могут вызвать приложения. Обнаружив, что программа обращается к одной из этих функций, линкер добавляет информацию о содержащей её DLL в целевой exe-файл. Позже, когда этот exe-файл будет запущен, загрузчик попытается спроектировать необходимую DLL на адресное пространство процесса; в случае неудачи весь процесс будет немедленно завершён.

При явном подключении (explicit linking) приложение вызывает функцию LoadLibrary(Ex), чтобы загрузить DLL, затем использует функцию GetProcAddress, чтобы получить указатели на требуемые функции, а по окончании работы с этими функциями вызывает FreeLibrary, чтобы выгрузить библиотеку и освободить занимаемые ею ресурсы.

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

В Visual C++ 6.0 появился ещё один способ подключения DLL, сочетающий в себе почти все достоинства двух рассмотренных ранее методов – отложенная загрузка DLL (delay-load DLL). Отложенная загрузка не требует поддержки со стороны операционной системы (а значит будет работать даже под Windows 95), а реализуется линкером Visual C++ 6.0.

При отложенной загрузке DLL загружается только тогда, когда приложение обращается к одной из содержащихся в ней функций. Это происходит незаметно для программиста (то есть вызывать LoadLibrary/GetProcAddress не требуется). После того как работа с функциями библиотеки завершена, её можно оставить в памяти или выгрузить посредством функции __FUnloadDelayLoadedDLL. Вызов этой функции – единственная модификация кода, которую может потребоваться сделать программисту (по сравнению с неявным подключением DLL). Если требуемая DLL не обнаружена, приложение аварийно завершается, но и здесь ситуацию можно исправить, перехватив исключение с помощью конструкции __try/__except. Как видим, отложенная загрузка DLL – весьма удобное средство для программиста.

Теперь рассмотрим, каким образом описанные способы подключения DLL используются на практике. Для этого будем считать, что нам требуется вызвать функцию X, экспортируемую библиотекой MyLib.dll. Пусть функция X имеет простейший прототип: void X(void);

Будем также считать, что библиотека импорта находится в файле MyLib.lib.

Неявное подключение

Первое, что нам нужно сделать – это передать линкеру имя библиотеки импорта нашей DLL. Для этого необходимо открыть окно настройки проекта (Project->Settings) и на вкладке Link дописать "MyLib.lib" в конец списка Object/Library modules. Альтернативный подход заключается в использовании директивы #pragma. В нашем случае необходимо вставить в код программы следующую строку:

#pragma comment(lib,"MyLib")

Второе, что нужно проделать – это добавить объявление функции в код программы (обычно объявления функций, экспортируемых DLL, сводятся в заголовочный файл – тогда требуется просто подключить его). Для нашей функции X объявление выглядит так:

__declspec(dllimport) void X(void);

Вот и всё. Теперь к функции X можно обращаться, как и к любой другой функции, статически прилинкованной к нашей программе:

X();

Явное подключение

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

Загружаем библиотеку:

HINSTANCE hLib = LoadLibrary("MyLib.dll");

Получаем указатель на функцию и вызываем её:

void (*X)();

(FARPROC &)X = GetProcAddress(hLib, "X");

X();

Выгружаем библиотеку из памяти:

FreeLibrary(hLib);

Отложенная загрузка

Сначала необходимо повторить шаги, которые мы проделывали при неявном подключении: передать линкеру имя библиотеки импорта и добавить в программу объявление функции X. Теперь, чтобы отложенная загрузка заработала, нужно добавить ключ линкера /DELAYLOAD:MyLib.dll и прилинковать к приложению библиотеку Delayimp.lib, реализующую вспомогательные функции механизма отложенной загрузки. Хотя эти опции можно добавить в настройки проекта, я предпочитаю использовать директивы #pragma:

#pragma comment(lib, "Delayimp")

#pragma comment(linker, "/DelayLoad:MyLib.dll")

Если после вызова функции X нам требуется выгрузить библиотеку MyLib.dll из памяти, можно воспользоваться функцией FUnloadDelayLoadedDLL. Чтобы эта функция работала корректно, необходимо добавить ещё один ключ линкера /DELAY:unload. Кроме того, нужно подключить заголовочный файл , в котором эта функция объявлена. Выглядеть это может примерно так:

#include <Delayimp.h>

#pragma comment(linker, "/Delay:unload")

.

.

X();

__FUnloadDelayLoadedDLL("MyLib.dll");

Имя, передаваемое функции FUnloadDelayLoadedDLL, должно в точности совпадать с именем, указанным в ключе /DELAYLOAD. Так, если передать ей имя "MYLIB.DLL", библиотека останется в памяти.

В заключение хочется отметить ещё один интересный момент. Когда я попытался воспользоваться отложенной загрузкой в своей программе, линкер отказался подключать библиотеку Delayimp.lib, выдавая сообщение о внутренней ошибке и подробную отладочную информацию. Чтобы решить эту проблему, я просто взял файлы Delayhlp.cpp и Delayimp.h из каталога Vc98\Include, добавил в файл Delayhlp.cpp строки:

PfnDliHook __pfnDliNotifyHook = NULL;

PfnDliHook __pfnDliFailureHook = NULL;

и перестроил эту библиотеку заново. После этого отложенная загрузка заработала нормально.

Ссылки

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

– December 1998, Microsoft systems journal, Win32 Q&A

– December 1998, Microsoft systems journal, Under the hood

– Linker support for delay-loaded DLLs

(Alexander Shargin (rudankort@mail.ru))
ВОПРОС-ОТВЕТ

Q. Все, наверное, знают программы, называемые Viewbar, которые показывают рекламные баннеры. Но вот как они ограничивают часть экрана, не позволяя другим окнам находиться поверх них? Например, если разрешение экрана 800×600, как они выделяют полосу сверху, в которой находятся, т.ч. программы, развернутые на полный экран, имеют высоту где-то на 60 пикселей меньше. Причем и немаксимизированные окна не могут "влезть" в эту полосу.

(Alexander Popov)

Для рассылки пока уникальный случай: на вопрос ответил сам автор.

A1 Спасибо за опубликование вопроса. Теперь я сам же могу на него ответить. Для создания приложения, похожего на панель задач или панель MS Office, используется AppBar. Последний достаточно хорошо описан в MSDN (см. Extend the Windows 95 Shell with Application Desktop Toolbars, Application Desktop Toolbars) А вообще, достаточно много интересного содержится в Windows Shell API, в частности: работа с панелью задач, как написать ScreenSaver, работа с панелью управления, Band Objects в Internet Explorer.

(Alexander Popov)

A2 Этого можно добиться, используя функцию SystemParametersInfo. У этой исключительно полезной функции существуют параметры SPI_GETWORKAREA и SPI_SETWORKAREA, позволяющие получить размер рабочей области экрана или установить для неё собственный размер (перед завершением работы программы его рекомендуется восстановить). Напрмер, следующий фрагмент "резервирует" полосу шириной в 100 пикселей в верхней части экрана:

CRect rcOld, rcNew;

SystemParametersInfo(SPI_GETWORKAREA, 0, (PVOID)&rcOld, 0);

rcNew = rcOld;

rcNew.top = 100;

SystemParametersInfo(SPI_SETWORKAREA, 0, (PVOID)&rcNew, 0);

Чтобы восстановить исходный размер, достаточно вызвать:

SystemParametersInfo(SPI_SETWORKAREA, 0, (PVOID)&rcOld, 0);

После того как нужная область зарезервирована, можно, например, поместить туда своё окно (вызовом CWnd::MoveWindow) и лишить пользователя возможности убрать его оттуда (так как в противном случае оно туда не вернётся), после чего рисовать в нём баннеры.

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

(Alexander Shargin (rudankort@mail.ru))

Фактически, ответы, конечно, одинаковые (в статье из MSDN как раз и используется SystemsParametersInfo), просто первый в отличие от самого ответа содержит ссылку на него.

Эти два ответа – все, что я получил. Два из восьми тысяч. Действительно, не очень-то сильно читатели хотят отвечать на вопросы. Так что я по всей видимости был прав по поводу поощрений… Господа! Прошу поактивнее! Или я могу решить что рубрика вам неинтересна и закрою ее…

Многие спрашивают, почему я лично не отвечаю на вопросы. Это неправда, иногда все-таки отвечаю ;)

Ну а главное: посмотрите Microsoft Systems Journal. Там человек В МЕСЯЦ отвечает на ДВА вопроса, причем это – его работа, т.е. он получает за это деньги. Потом, далеко не на всякий вопрос можно сходу дать однозначный ответ. Как правило, те вопросы, для которых можно это сделать – неинтересны. Так что наверное гораздо эффективнее  разделять эту задачу  с читателями.

ОБРАТНАЯ СВЯЗЬ

Alexander Shargin по поводу ответа A1 (Sergey Emantayev) из №21 пишет:

Справедливости ради следует отметить, что всплывающие меню не обновляются в Idle loop'е. В нём обновляются тулбары, статус бар и т.п., но всплывающие меню обновляются только в ответ на WM_INITMENUPOPUP. Этой практики следует придерживаться и в собственных приложениях.

Продолжается дискуссия о комментариях:

Привет, что касается комментариев, то не кажется ли вам, господа, что сам код (естественно, грамотный код) и является самым лучшим и лаконичным комментарием к программе. И вообще, мне кажется, что комментарии нужны только тем людям, которые не писали конкретную программу, и причем, комментировать следует, только ключевые моменты, а если человек просто не понимает кода, то комментируй, не комментируй, это по барабану. Года два назад, я писал, одну небольшую софту, она работала под DDraw в полно-экранном режиме, я колбасил интерфейс a-la X-window Linux, фунций, переменных было море, и я закоментировал только, что и какая переменная делает, недавно, пришлось вернутся к этому коду, я его передал другому, человеку, немного видоизменив. И никаких проблем с восстановлением (в мозгу) архитектуры программы не было, как будто я с ней не работал не два года, а два дня. Код — это и сеть лучший комментарий, ну иногда желательно и присутствие "бумажной" модели программы.

(Anton Palagin)

У меня к Вам предложение по Вашей рассылки "Vsiual C++" – не могли бы Вы в поле сабжа каждой рассылки в конце ставить темы, которые рассматриваются в данном номере рассылки (например : "WinInet, Tray, Закладки"). Так будет значительно удобнее ориентироваться в архиве рассылке при поиске нужной темы. А то вот искал тот номер, где рассматривался вопрос с помещением программы в Tray и пришлось потратить некоторое время (линейно зависящее от количества вышедших номеров рассылки). А так посмотрел на заголовок и все понятно. А то поиск по сообщениям в Outlook Express глючный какой-то и нифига не находит.

(Даниил Иванов)

Очень разумное предложение, на мой взгляд. Принял к исполнению – взгляните на subject этого выпуска. Спасибо, Даниил! Правда, в заголовок выношу только главную тему выпуска. Побочные придется искать по-старому.

В ПОИСКАХ ИСТИНЫ

Q. Все знают десктопные программы-ассистенты (screenmates/deskmates, MS Agent). Весь вопрос, что качественных, без артифактов, достаточно мало. Для реализации экранного помошника есть 2 различных подхода (если знаете еще, подскажите): – рисовать поверх десктопа, запоминать-востанавливать фон и т.д. Здесь сложно уследить за случаями, когда другие окна перекрывают место, где выводится текущий кадр персонажа, если на десктопе идет своя жизнь (меняются-появляются иконки, молчу про Drag'n'Drop) – использовать регионы, примеры есть на codeguru, но это достаточно трудоемкая штука – идея проста: создать полностью прозрачное окно и рисовать в нем просто текущий кадр с действием персонажа, не заботясь о том, на каком фоне его рисуешь, ведь окно прозрачное! Т.е. программа может просто рисовать постоянно меняющиеся картинки в таком прозрачном окне и это создаст эффект анимации персонажа, главное тут, чтобы любые изменения фона не влияли, т.е. просто добавление атрибута WS_EX_TRANSPARENT – это не то что нужно

Так вот, внимание, вопрос!

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

Кстати, на кодегуру прямого примера нет точно, а то что есть о том как рисовать прозрачные штучки – не то.

(Valery Boronin)
АНОНС

Читайте в следующем выпуске

Многозадачность в Windows: теория и практика


Успехов в программировании!

(Алекс Jenter jenter@mail.ru) (Красноярск, 2000.)







Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх