Зачем использовать пакеты?
Ответ прост: из-за мощности пакета. Пакеты времени разработки упрощают выпуск и установку пользовательских компонентов; пакеты времени выполнения привносят новые возможности в традиционное программирование. После того как вы скомпилируете повторно используемый код в библиотеку времени выполнения, вы сможете использовать его в нескольких приложениях. Все приложения могут обращаться к стандартным компонентам через пакеты, и Delphi сам это делает. Поскольку приложению не приходится копировать отдельную библиотеку компонентов в исполняемый файл, это существенно экономит системные ресурсы и дисковое пространство. Кроме того, пакеты сокращают время, затрачиваемое на компиляцию, поскольку вам нужно компилировать только код конкретного приложения.
Если пакеты можно будет использовать динамически, мы сможем получить больше преимуществ. Пакеты обеспечивают новый модульный подход к разработке приложений. Иногда вам может потребоваться сделать определенные модули дополнительными компонентами приложения, например систему учета, которая поставляется с дополнительным модулем управления персоналом. В некоторых случаях вам необходимо установить только базовое приложение, а в других случаях может потребоваться установка дополнительных модулей HR. Этот модульный подход можно легко реализовать с помощью пакетной технологии. Раньше этого можно было достичь только путем динамической загрузки DLL, но, используя технологию упаковки Delphi, вы можете «упаковать» каждый тип модуля приложения в пакеты. В частности, объекты классов, созданные из пакетов, принадлежат приложению и поэтому могут взаимодействовать с объектами в приложении.
Пакеты и приложения среды выполнения
Многие разработчики думают о пакетах Delphi только как о месте для размещения компонентов, хотя на самом деле пакеты могут (и должны) использоваться при проектировании модульных приложений.
Чтобы продемонстрировать, как использовать пакеты для модульной организации вашего приложения, давайте создадим пример:
1. Создайте новую программу Delphi с двумя формами: Form1 и Form2;
2. Удалить Form2 из списка автоматически создаваемых форм (PROject | Параметры | Формы);
3. Поместите кнопку на форму Form1 и введите следующий код в обработчик события OnClick кнопки:
с TForm2.Create(приложение) сделайте
начинать
ПоказатьМодал;
Бесплатно;
Конец;
4. Не забудьте добавить Unit2 в раздел использования Unit1;
5. Сохраните и запустите проект.
Мы создали простое приложение, отображающее форму с кнопкой, при нажатии которой создается и отображается другая форма.
Но что нам делать, если мы хотим включить Form2 из приведенного выше примера в модуль многократного использования и заставить его работать нормально?
Ответ: Бао!
Для создания пакета для Form2 требуется следующая работа:
1. Откройте Менеджер проектов (Вид | Менеджер проектов);
2. Щелкните правой кнопкой мыши группу проектов и выберите «Добавить новый проект...»;
3. В списке проектов «Новый» выберите «Пакет»;
4. Теперь вы сможете увидеть редактор пакетов;
5. Выберите пункт «Содержит» и нажмите кнопку «Добавить»;
6. Затем нажмите кнопку «Обзор...» и выберите «Unit2.pas»;
7. Пакет теперь должен содержать модуль Unit2.pas;
8. Наконец сохраните и скомпилируйте пакет.
Теперь мы завершили пакет. В каталоге Project/BPL должен быть файл с именем «package1.bpl». (BPL — это сокращение от Borland Package Library, а DCP — это сокращение от Delphi CompiledPackage.)
Этот пакет завершен. Теперь нам нужно включить переключатель параметров пакета.
и перекомпилируйте исходное приложение.
1. Дважды щелкните «Project1.exe» в диспетчере проектов, чтобы выбрать проект;
2. Щелкните правой кнопкой мыши и выберите «Параметры...» (вы также можете выбрать в меню Проект | Параметры...);
3. Выберите страницу опций «Пакеты»;
4. Установите флажок «Сборка с использованием пакетов времени выполнения»;
5. Отредактируйте поле редактирования «Пакеты времени выполнения»: «Vcl50;Package1» и нажмите кнопку «ОК»;
6. Примечание. Не удаляйте Unit2 из приложения;
7. Сохраните и запустите приложение.
Приложение будет работать, как и раньше, но разницу можно будет увидеть в размере файла.
Размер Project1.exe теперь составляет всего 14 КБ по сравнению с предыдущими 293 КБ. Если вы используете браузер ресурсов для просмотра содержимого файлов EXE и BPL, вы увидите, что DFM и код Form2 теперь сохранены в пакете.
Delphi выполняет статическое связывание пакетов во время компиляции. (Вот почему вы не можете удалить Unit2 из проекта EXE.)
Подумайте, что вы от этого можете получить: вы можете создать в пакете модуль доступа к данным, а при изменении правил доступа к данным (например, переключении с BDE-соединений на ADO-соединения) слегка модифицировать и переопубликовать пакет. Альтернативно вы можете создать форму в одном пакете, которая отображает сообщение «Этот параметр недоступен в текущей версии», а затем создать полнофункциональную форму в другом пакете с тем же именем. Теперь у нас есть продукт в версиях «Pro» и «Enterprise» без каких-либо усилий.
Динамическая загрузка и выгрузка пакетов
В большинстве случаев статически связанной DLL или BPL будет достаточно. Но что, если мы не хотим выпускать BPL? «Библиотека динамической компоновки Package1.bpl не может быть найдена в указанном каталоге» — единственное сообщение, которое мы можем получить до завершения работы приложения. Или в модульном приложении мы можем использовать любое количество плагинов?
Нам необходимо динамически подключаться к BPL во время выполнения.
Для DLL есть простой метод — использовать функцию LoadLibrary:
функция LoadLibrary(lpLibFileName: Pchar): HMODULE;stdcall;
После загрузки DLL мы можем использовать функцию GetProcAddress для вызова экспортированных функций и методов DLL:
функция GetProcAddress (hModule: HMODULE; lpProcName: LPCSTR): FARPROC;
Наконец, мы используем FreeLibrary для удаления DLL:
функция FreeLibrary(hLibModule: HMODULE): BOOL;stdcall;
В следующем примере мы динамически загружаем библиотеку Microsoft HtmlHelp:
функция TForm1.ApplicationEvents1Help (Команда: Слово; Данные: Целое число; вар CallHelp: Логическое значение): Логическое значение;
тип
TFNHtmlHelpA = функция (hwndCaller: HWND; pszFile: PansiChar; uCommand: UINT; dwData: Dword): HWND;
вар
Модуль справки: Hmodule;
Хтмлхелп: ТФНХтмлхелпА;
начинать
Результат: = Ложь;
HelpModule := LoadLibrary('HHCTRL.OCX');
если HelpModule <> 0, то
начинать
@HtmlHelp := GetProcAddress(HelpModule, 'HtmlHelpA');
если @HtmlHelp <> ноль, то
Результат: = HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0;
FreeLibrary (Модуль справки);
конец;
CallHelp := Ложь;
конец;
Динамическая загрузка BPL
Мы можем использовать тот же простой метод и для борьбы с BPL, или, лучше сказать, по сути, таким же простым способом.
Мы можем динамически загружать пакеты с помощью функции LoadPackage:
функция LoadPackage (имя константы: строка): HMODULE;
Затем используйте функцию GetClass для создания объекта типа TPersistentClass:
функция GetClass(const AclassName: string):TPersistentClass;
После того, как все будет сделано, используйте UnLoadPackage(Module:HModule);
Давайте внесем небольшие изменения в исходный код:
1. Выберите «Project1.exe» в менеджере проектов;
2. Щелкните правой кнопкой мыши и выберите «Параметры...»;
3. Выберите страницу опций «Пакеты»;
4. Удалите «Пакет1» из поля редактирования «Пакеты времени выполнения» и нажмите кнопку «ОК»;
5. На панели инструментов Delphi нажмите кнопку «Удалить файл из проекта»;
6. Выберите «Unit2 | Form2» и нажмите «ОК»;
7. Теперь в исходном коде «Unit1.pas» удалите Unit2 из раздела «uses»;
8. Введите временной код OnClick для Button1;
9. Добавьте две переменные типа HModule и TPersistentClass:
вар
ПакетМодуль: HModule;
Класс: TPersistentClass;
10. Используйте функцию LoadPackage для загрузки пакета Pacakge1:
PackageModule := LoadPackage('Package1.bpl');
11. Проверьте, равен ли PackageModule 0;
12. Используйте функцию GetClass для создания постоянного типа:
AClass := GetClass('TForm2');
13. Если этот постоянный тип не равен нулю, мы можем вернуться к предыдущему
Аналогично создавайте и используйте объекты этого типа:
с TComponentClass(AClass).Create(Application) как TcustomForm
начинать
ПоказатьМодал;
Бесплатно;
конец;
14. Наконец, используйте процесс UnloadPackage, чтобы удалить пакет:
ВыгрузитьПакет(МодульПакета);
15. Сохраните проект.
Вот полный список обработчиков событий OnClick:
процедура TForm1.Button1Click(Отправитель: Объект);
вар
ПакетМодуль: HModule;
Класс: TPersistentClass;
начинать
PackageModule := LoadPackage('Package1.bpl');
если PackageModule <> 0, то
начинать
AClass := GetClass('TForm2');
если AClass <> ноль, то
с TComponentClass(AClass).Create(Application) как TcustomForm
начинать
ПоказатьМодал;
Бесплатно;
конец;
ВыгрузитьПакет(МодульПакета);
конец;
конец;
К сожалению, это еще не все.
Проблема в том, что функция GetClass может искать только зарегистрированные типы. Классы форм и классы компонентов, на которые обычно ссылаются в форме, автоматически регистрируются при загрузке формы. Но в нашем случае форму невозможно загрузить раньше. Так где же нам зарегистрировать тип? Ответ: в сумке. Каждый модуль в пакете инициализируется при загрузке пакета и очищается при его выгрузке.
Теперь вернемся к нашему примеру:
1. Дважды щелкните «Package1.bpl» в менеджере проектов;
2. Нажмите знак + рядом с надписью «Unit2» в разделе «Содержит»;
3. Дважды щелкните «Unit2.pas», чтобы активировать редактор исходного кода модуля;
4. Добавьте раздел инициализации в конец файла;
5. Используйте процедуру RegisterClass для регистрации типа формы:
РегистрКласс(TForm2);
6. Добавьте раздел доработки;
7. Используйте процедуру UnRegisterClass, чтобы отменить регистрацию типа формы:
УнРегистерКласс(TForm2);
8. Наконец, сохраните и скомпилируйте пакет.
Теперь мы можем безопасно запустить «Project1», и он будет работать так же, как и раньше, но теперь вы можете загружать пакеты так, как захотите.
конец
Помните, независимо от того, используете ли вы пакеты статически или динамически, включите Project | Options | Build with runtime packages.
Прежде чем удалить пакет, не забудьте уничтожить все объекты классов в пакете и отменить регистрацию всех зарегистрированных классов. Следующий процесс может вам помочь:
процедура DoUnloadPackage (Модуль: HModule);
вар
я: целое число;
М: TMemoryBasicInformation;
начинать
for i := Application.ComponentCount - от 1 до 0 сделать
начинать
VirtualQuery(GetClass(Application.Components[i].ClassName), M, Sizeof(M));
если (Модуль = 0) или (HMODULE(M.AllocationBase) = Модуль), то
Приложение.Компоненты[i].Бесплатно;
конец;
UnregisterModuleClasses(Модуль);
ВыгрузитьПакет(Модуль);
конец;
Перед загрузкой пакета приложению необходимо знать имена всех зарегистрированных классов. Один из способов улучшить эту ситуацию — создать механизм регистрации, который сообщает приложению имена всех классов, зарегистрированных пакетом.
Пример
Несколько пакетов. Пакеты не поддерживают циклические ссылки. То есть модуль не может ссылаться на модуль, который уже ссылается на этот модуль (хе-хе). Это затрудняет установку определенных значений в вызывающей форме вызываемым методом.
Решением этой проблемы является создание дополнительных пакетов, на которые ссылаются как вызывающий объект, так и объекты в пакете. Представляете, как мы сделаем Application владельцем всех форм? Переменная Application создается в Forms.pas и включается в пакет VCL50.bpl. Возможно, вы заметили, что вашему приложению необходимо не только скомпилировать VCL50.pas, но и включить VCL50 в ваш пакет.
В нашем третьем примере мы разрабатываем приложение для отображения информации о клиентах и, по требованию, заказов клиентов (динамически).
Итак, с чего мы можем начать? как и все приложения баз данных
Процедура та же, нам нужно подключиться. Мы создаем основной модуль данных, содержащий соединение TDataBase. Затем мы инкапсулируем этот модуль данных в пакет (cst_main).
Теперь в приложении мы создаем форму клиента и ссылаемся на DataModuleMain (статически связываем VCL50 и cst_main).
Затем мы создаем новый пакет (cst_ordr), содержащий форму заказа клиента и требующий cst_main. Теперь мы можем динамически загружать cst_ordr в приложение. Поскольку основной модуль данных уже существует до загрузки динамического пакета, cst_ordr может напрямую использовать экземпляр основного модуля данных приложения.
На рисунке выше представлена функциональная схема этого приложения:
Сменные пакеты. Еще одним вариантом использования пакетов является создание заменяемых пакетов. Реализация этой функциональности не требует возможностей динамической загрузки пакета. Предположим, мы хотим выпустить ограниченную по времени пробную версию программы, как этого добиться?
Сначала мы создаем форму «Заставка», обычно картинку со словом «Пробная версия», и отображаем ее во время запуска приложения. Затем мы создаем форму «О программе», которая предоставляет некоторую информацию о приложении. Наконец, мы создаем функцию, которая проверяет, не устарело ли программное обеспечение. Мы инкапсулируем эти две формы и эту функцию в пакет и выпускаем его вместе с пробной версией программного обеспечения.
Для платной версии мы также создаем форму «Всплеск» и форму «О программе» — с теми же именами классов, что и предыдущие две формы — и тестовую функцию (которая ничего не делает) и добавляем их инкапсулированные в пакет с тем же имя.
Что что? Вы спросите, полезно ли это? Что ж, мы можем выпустить для публики пробную версию программного обеспечения. Если клиент покупает приложение, нам нужно отправить только непробный пакет. Это значительно упрощает процесс выпуска программного обеспечения, поскольку требуется только одна установка и одно обновление регистрационного пакета.
Пакет открывает еще одну дверь к модульному проектированию для сообществ разработчиков Delphi и C++ Builder. С пакетами вам больше не нужно передавать дескрипторы окон, функции обратного вызова и другие технологии DLL. Это также сокращает цикл разработки модульного программирования. Все, что нам нужно сделать, это позволить пакетам Delphi работать на нас.