17 декабря 2008 г.

Создаём систему плагинов, часть 4

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

Прежде чем переходить к вопросам загрузки плагинов и самим интерфейсам, я бы хотел ещё раз сказать, что пакеты - это просто DLL (а после этого поста вы наверняка сами догадаетесь, как нужно правильно загружать плагины в виде "пакетов в виде DLL").

Чтобы это надёжно закрепилось в голове, я предлагаю рассмотреть обратную схему. Предположим, у нас есть программа, использующая схему с классическими плагинами в виде DLL - например, Total Commander (как будет видно чуть позже, это не самый удачный пример, но другого мне навскидку в голову не приходит).

Итак, мы хотим написать для него плагин. И используем в плагине библиотеку с проблемами в DllMain.

Мы можем использовать пакеты для реализации плагина!

Действительно. Пакет - это просто DLL. Ничто не мешает нам реализовать плагин в виде пакета. Единственные условия - это наличие в протоколе плагинов отдельных функций инициализации и финализации. А также отсутствие экспортируемых функций с именами Initialize и Finalize (*).

Как это реализовать. Просто. Берём обычный плагин в виде DLL и делаем такие манипуляции: переименовываем dpr-файл в pas-файл и меняем его структуру соответствующим образом (удаляем строку project, добавляем unit, interface, implementation). Секция interface будет пустой, а секция implementation будет содержать весь код из dpr-файла - да, включая секцию exports (если в плагине используется DLLProc/DLLProcEx, то этот код перетащите в секции initialization/finalization модуля: DLLProc/DLLProcEx для пакета не вызываются, т.к. эта процедура в пакете практически пуста - см. SysInit._InitPkg - это ровно она и есть). Далее, в этот же модуль добавляются функции _Init и _Done, а их вызовы вставляются первым и последним действием в соответствующие функции протокола плагинов.

Наконец создаётся пустой пакет, из него удаляются все зависимости и добавляется модуль, который был получен конвертированием dpr-файла (если вас раздражают warning-и типа "Unit 'XXX' implicitly imported into package 'YYY', то добавьте в пакет и каждый такой модуль, но это не обязательно).
Готово.

Мы только что сконвертировали плагин из вида DLL-плагина в вид пакетного плагина. Он ничем не отличается от плагина в виде DLL - только что не имеет упоминаемой проблемы с DllMain. Делаем компиляцию и используем.

Теперь. Что касается Total Commander. К сожалению, в протоколе плагинов Total Commander (я буду говорить про FS-плагины) не предусматривается функция для финализации. Для инициализации есть FsInit - именно в эту функцию нужно первым действием вставить вызов _Init. Но вот вызов _Done поместить совершенно некуда - увы. В принципе это не слишком страшно - Total Commander выгружает плагины только при выходе из программы (за исключением случая отладки плагинов с использованием команды форсированной выгрузки). Поэтому отсутствие финализации не так страшно - система за нами подчистит. Но в других случаях (если бы плагины выгружались в процессе самой работы) это было бы непригодно - после выгрузки нашего плагина за ним остались бы ресурсы (утечка ресурсов). Ведь DLLProc в пакете практически нет, а никакой функции финализации протоколом плагинов не вызывается - просто некому чистить ресурсы (**).

Читать далее.

(*) Строго говоря, последнее ограничение (отсутствие в протоколе функций с именами Initialize и Finalize) не является финальным show-stopper-ом. Всегда можно создать библиотеку-заглушку, которая все вызовы будет просто перенаправлять на реальный плагин и при этом такие имена будут свободны для использования, т.к. DLL не является пакетом. При этом, конечно, появится два файла. Да, это плохо в смысле удобства распространения. В принципе, можно у готового пакета подпатчить имена функций, но это значит что перед использованием пакета для него нужно будет запускать какую-то утилиту. Плюс отсутствие готовой реализации. Короче, либо это два файла с библиотекой-заглушкой, либо отказаться от пакетов.

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

(**) Вообще-то в Delphi есть процедура AddModuleUnloadProc, позволяющая добавить обработчик на выгрузку модуля. Теоретически, это событие можно использовать для размещения в нём вызова _Done (разумеется, с проверкой, что выгружается именно главный модуль плагина, а не какой-то ещё). Но реально я этот способ не проверял. Кроме того, нужно понимать, что фактически этот код будет выполняться из DllMain, а значит - со всеми вытекающими последствиями. Так что если протокол не предусматривает отдельной функции финализации - здесь вы действительно мало что можете сделать. Поэтому, если вы проектируете свою систему плагинов - предусмотрите в ней эти две функции. Их сделать не сложно, зато вы избавите авторов своих плагинов от возможных проблем - кто знает, как они захотят реализовывать свои плагины.

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

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

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

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

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

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

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