понедельник, 5 января 2009 г.

Несколько причин, чтобы не делать ничего страшного в своей DllMain

Это перевод Some reasons not to do anything scary in your DllMain. Автор: Реймонд Чен.

Как сегодня уже все знают, вам не следует делать ничего даже отдалённо интересного в своей функции DllMain (*). Олег Львович написал об этом две очень хорошие статьи: одна о том, как всё это работает, а вторая - что может пойти не так (**).

А вот и ещё одна причина, почему в DllMain не следует делать ничего интересного: часто библиотеку загружают только временно, без вызова её полной функциональности. Например, кто-нибудь может использовать вашу библиотеку вот так:
  // Проверка ошибок убрана для простоты чтения
  Lib := LoadLibrary('you');
  Ico := LoadIcon(Lib, MAKEINTRESOURCE(5));
  FreeLibrary(Lib);
Этот код просто хочет от вас иконку. Он был бы весьма удивлён (а, может быть, даже расстроен), если бы ваша DLL в это время начала делать что-то тяжеловесное. Например, запускать таймер или поток.

(Да, конкретно этого можно избежать, используя LoadLibraryEx с LOAD_LIBRARY_AS_DATAFILE, но сейчас речь не об этом).

Другим случаем, когда библиотека загружается, но не используется - когда она загружается как чья-либо зависимость. Предположим, что 'middle' - это имя какой-то промежуточной DLL, которая статически ссылается на вашу DLL:
  Lib := LoadLibrary('middle');
  Proc := GetProcAddress(Lib, 'SomeFunction');
  Proc(...);
  FreeLibrary(Lib);
Когда загружается библиотека 'middle', ваша DLL будет также загружена и инициализирована. Получается, что ваша инициализация выполняется, даже если процедура SomeFunction не использует вашу DLL.

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

Ещё один пример - открытие Панели Управления. Панель Управления загружает каждый *.cpl файл для вызова их функций CplApplet для определения иконки для отображения. Опять-таки, обычно для этого ваша DLL не нужна.

И ни при каких обстоятельствах вы не должны создавать объекты с привязкой к потокам (thread-affinity) в своём обработчике DLL_PROCESS_ATTACH (***). Вы не сможете проконтролировать, какой поток отправит DLL_PROCESS_ATTACH, а какой - DLL_PROCESS_DETACH. Поток, который отправлял сообщение DLL_PROCESS_ATTACH, может завершиться сразу после загрузки вашей DLL. Тогда любой объект с привязкой к нему перестанет работать, потому что его владелец завершил работу.

А даже, если поток продолжит работу, то нет никаких гарантий, что FreeLibrary вызовет тот же поток, который вызывал LoadLibrary. Поэтому вы не сможете очистить объекты, привязанные к потоку в обработчике DLL_PROCESS_DETACH, поскольку вы будете оперировать не с тем потоком.

И абсолютно ни при каких обстоятельствах вам не следует делать ничего такого сумасшедшего, как создание окна внутри DLL_PROCESS_ATTACH. В дополнение к проблемам привязок к потокам, тут добавляется проблема глобальных ловушек (global hooks). Хуки, работающие при блокировке загрузчика ОС, - это верный рецепт катастрофы. Поэтому не удивляйтесь мёртвым блокировкам на своей машине после такого.

Завтра будут ещё примеры.

Примечания переводчика:
(*) В Delphi DllMain доступна вам через переменные DllProc(Ex). Заметим, что в DLL Delphi в DllMain выполняются секции initialization и finalization всех модулей (unit), а также код между begin и end в dpr файле. Пакеты Delphi работают по-другому.
(**) Ещё можно почитать mgrier's WebLog.
(***) Например, создание объекта в глобальной threadvar-переменной.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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