22 декабря 2008 г.

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

Самое время поговорить о серверной части. А начнём мы с загрузки плагинов.

После того, как мы столь подробно разжевали наши плагины, и вы действительно осознали фразу "пакеты = DLL", то на вопрос "а как их загружать?" у вас уже должен быть ответ: "как DLL!" - т.е. через LoadLibrary.

Позвольте, воскликнет кто-то, но ведь для загрузки пакетов есть функция LoadPackage!

Давайте посмотрим её код и выясним отличия от LoadLibrary. Можно ещё посмотреть справку.
Итак, LoadPackage делает следующее:
  1. загружает бинарный модуль (module) пакета (привет, LoadLibrary).
  2. проверяет наличие повторов модулей Delphi (unit) в пакете (*).
  3. вызывает секции initialization модулей Delphi (unit) в пакете (вызов той самой экспортируемой функции 'Initialize').
Как видим, LoadPackage - это LoadLibrary + дополнительные действия. И из-за этих доп. действий LoadPackage нам и не подходит. В частности потому, что она вызывает 'Initialize'. Нашим протоколом плагинов не предусмотрено никакого вызова 'Initialize'. Нами предусмотрен вызов '_GetPluginInitInterface' для получения интерфейса, а потом уже вызов метода Init у самого интерфейса.

Окей, тогда наш план выглядит вот так:
  1. вызываем LoadLibrary для загрузки модуля пакета (**).
  2. вызываем '_GetPluginInitInterface' для получения интерфейса (в этот момент эта функция сама вызовет 'Initialize' пакета - этим в ней занимается функция _Init).
  3. вызываем метод Init интерфейса.
А что там насчёт пункта "проверяет наличие повторов" у LoadPackage?
Каждый пакет может зависеть от некоторого числа других пакетов (то самое "requires"). Каждый пакет также содержит один или несколько модулей Delphi. Проблема в том, что может случится множество Плохих Вещей (случайные AV и т.п.), если вы загрузите пакеты, содержащие один и тот же модуль (unit) в двух разных пакетах.

Чтобы этого не произошло, RTL Delphi везде использует функцию LoadPackage для загрузки пакетов, в которой и осуществляется проверка, что загружаемый модуль (module) не содержит повторов. Если обнаруживается конфликт, LoadPackage возбуждает исключение типа EPackageError с сообщением "Cannot load package 'XXX'. It contains unit 'YYY' which is also contained in package 'ZZZ'." (пакет при этом, разумеется, не загружается).

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

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

(*) Да, я знаю, что две различные сущности - module (двоичный исполняемый модуль, т.е. EXE/DLL) и unit (модуль Delphi, т.е. pas-файл) на русский переводятся одинаково: "модуль". Поэтому на всякий случай, чтобы не возникло недоразумений, я подписываю в скобках, что имеется ввиду. Хотя обычно из контекста это и так понятно.

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

(***) Конечно, кто-то может собрать плагин в виде обычного пакета - с requires других пакетов. Что будет при использовании таких плагинов? Будет ли это работать? Но об этом потом.

3 комментария :

  1. Большое спасибо за набор статей, жду с нетерпением продолжения.Одно интересно, я смогу использовать классы которые хранятся моих bpl?

    ОтветитьУдалить
  2. >>> я смогу использовать классы которые хранятся моих bpl?
    Понятия не имею :D
    Скорее всего без проблем, но надо смотреть...
    В любом случае этот цикл ещё далеко не закончен.

    ОтветитьУдалить
  3. с нетерпением жду продолжения, вот если б к новому году завершить... :)

    ОтветитьУдалить

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

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

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

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

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