Главная » Delphi » Версии пакетов

0

Версию (versioning) пакета  часто  понимают неправильно. похожи на версии модулей. Другими  словами, любой  пакет  приложения должен  быть скомпи лирован с использованием той  же версии Delphi, что  и само приложение. Таким  об разом, пакет,  написанный в Delphi 6, нельзя  использовать с приложением, созданным в Delphi  5. Разработчики компании Borland обычно называют версию пакета  базой кода (code base). Поэтому  пакет,  созданный в Delphi 6, можно  назвать написанным на базе кода 6.0. Эту идею можно отразить в соглашении об именах, используемых для файлов пакетов.

Директивы компилятора для пакетов

Существует несколько директив компилятора, предназначенных специально для использования в исходном коде  пакетов. Одни  из них  относятся к модулям пакетов, другие — к файлам пакетов. Эти директивы приведены в табл. 14.3 и 14.4.Таблица 14.3. Директивы компилятора для модулей пакетаДиректива                Назначение{$G} или

{$IMPORTEDDATA OFF}Используется для предотвращения помещения модуля в пакет,  т.е. в том случае, когда модуль должен  быть ском пилирован непосредственно с приложением. Противо положна директиве {$WEAKPACKAGEUNIT}, позволяю щей   модулю  входить   в  состав   пакета,  код   которого связан  с приложением статически

{$DENYPACKAGEUNIT}       То же самое, что и {$G}

{$WEAKPACKAGEUNIT}       Описано в следующем разделе

Таблица 14.4. Директивы компилятора для файлов .dpk пакета

Директива                                            Назначение

{$DESIGNONLY ON}         Компилирует пакет только  как пакет разработки

{$RUNONLY ON}       Компилирует пакет только  как пакет времени выполнения

{$IMPLICITBUILD OFF}     Предотвращает перекомпоновку  пакета  в дальнейшем.

Используйте  эту  возможность  в  пакетах, которые  не придется изменять часто

Подробней о директиве {$WEAKPACKAGEUNIT}

Концепция слабого пакета (weak package) очень  проста. Обычно она используется, если  пакет  ссылается на библиотеки DLL,  которые могут отсутствовать. Например, Vcl60 обращается к API Win32 ядра,  входящего в состав  ряда  операционных систем Windows (присутствует у NT/2000, а у 95/98 отсутствует). Многие  из вызываемых ей подпрограмм находятся в библиотеках DLL, присутствующих далеко  не на каждом компьютере.    Эти    вызовы   распознаются    модулями,     содержащими    директиву

{$WEAKPACKAGEUNIT}. Наличие такой директивы приводит к тому, что исходный код данного модуля будет помещен в пакете  в файл  DCP, а не в BPL (файл .DCP —  аналог файла  .DCU, а файл  .BPL —  аналог  файла  .DLL). Таким  образом, любые  ссылки  на функции этих  “слабо пакетированных” модулей будут статически связаны с приложе нием, в отличие от динамических ссылок на пакет.

Директива {$WEAKPACKAGEUNIT} употребляется достаточно редко (возможно, с ней и вовсе не придется работать). Она позволяет разработчикам Delphi  отрабатывать спе цифические ситуации. Предположим, существует два компонента (каждый в отдельном пакете), ссылающихся на один и тот же модуль интерфейса библиотеки DLL. Если при ложение использует оба компонента, то это приведет к загрузке  двух экземпляров биб лиотеки DLL, что,  в свою  очередь, вызовет конфликты инициализации и использова ния глобальных переменных. Решением данной проблемы будет помещение модуля ин терфейса в  один  из  стандартных пакетов Delphi   (типа   Vcl60.bpl).  Однако   такой подход не позволяет избавиться от других проблем, связанных с отсутствием специали зированных библиотек DLL (например PENWIN.DLL). Если библиотека DLL, указанная в модуле интерфейса,  отсутствует, то пакет  Vcl60.bpl (а значит, и Delphi) окажутсябесполезными. Разработчики Delphi ликвидируют эту опасность, позволяя Vcl60.bpl содержать модуль интерфейса в отдельном пакете, который подключается статически и не загружается динамически при работе с Vcl60.bpl в среде Delphi.

Как уже отмечалось, на практике вряд ли придется прибегнуть к этой директиве, ес

ли только  не предвидится ситуация, с которой довелось столкнуться создателям Delphi, или если не понадобится удостовериться в том, что определенный модуль включен в па кет, но при этом с использующим его приложением статически связан.  Это можно  при менить, например, для оптимизации работы приложения. Обратите внимание: любые слабо пакетированные модули не могут обладать  глобальными переменными или кодом в своих разделах инициализации и завершения. Кроме  того,  для слабо пакетированных модулей вместе с пакетами следует распространять и файлы *.dcu.

Соглашения об именах пакетов

1

 

Как уже говорилось, версии пакетов должны  отражаться в их именах.  Настоятельно рекомендуем (хотя  вы и не обязаны это делать)  использовать в соглашении об именах пакетов базу кода. Например, компоненты этой  книги  находятся в пакете  времени вы полнения DdgRT6.dpk, имя  которого содержит базу кода  Delphi 6 —  6. То  же  самое можно  сказать  и о пакете разработки DdgDT6.dpk. Предыдущей версией этого  пакета была бы DdgRT5.dpk . Используя это  соглашение, вы избавляете своих  пользователей от  неприятностей,  связанных с согласованием версии пакета  и  компилятора Delphi. Имя  нашего  пакета  начинается с трехсимвольного идентификатора автора/компании (Ddg), за которым следуют символы RT (для пакета  времени выполнения) или DT (для пакета  разработки). Вы можете следовать любому соглашению, отвечающему вашим требованиям, но все же включайте в имена пакетов номер версии Delphi.

Расширяемые приложения, использующие пакеты времени выполнения (дополнения)

Пакеты дополнений (add  in packages) позволяют дробить приложения на части  (или модули),  которые будут распространяться отдельно от основного приложения. Такая схема  оказывается особенно привлекательной, поскольку  позволяет расширять воз можности приложения без внесения изменений в код всего приложения и повторной компиляции. Однако  подобный подход  требует  тщательного архитектурного плани рования.  Углубление   в  вопросы такой   разработки  выходит за  рамки   этой   книги. В данном разделе приведена лишь иллюстрация этого метода.

1 Авторы абсолютно правы —  правила необходимо соблюдать! На самом деле прежней версией пакета разработки был DDGStd50.dpk, где “за трехсимвольным идентификатором автора/компании (DDG) сле дуют символы Std (для пакета времени выполнения) или Dsgn (для пакета разработки)”. — Прим. ред.

Создание форм дополнений

Здесь приложение разбито на три логические части:  главное приложение (ChildTest.exe),  пакет,   содержащий  класс  TChildForm (AIChildFrm6.bpl),  и конкретные классы,  производные от класса  TChildForm, каждый  из которых распо ложен  в своем собственном пакете.

Пакет   AIChildFrm6.bpl включает в себя  абстрактный базовый класс  TChild- Form. В других пакетах  содержатся конкретные классы  или производные от TChild- Form (например TChildForm). Таким  образом, авторы называют эти  пакеты соот ветственно абстрактным пакетом  (base  package) и конкретными  пакетами (concrete packages).

Главное  приложение использует абстрактный пакет  (AIChildFrm6.bpl). Каждый конкретный пакет также использует абстрактный пакет. Для обеспечения корректной работы главное приложение должно  быть  скомпилировано с пакетами времени вы полнения, включая  пакет AIChildFrm6.dcp. Аналогично, каждый  конкретный пакет должен  использовать пакет  AIChildFrm6.dcp. Не  будем приводить здесь код исход ного  класса TChildForm или его конкретных потомков, но отметим, что  каждый  мо дуль класса,  производного от TChildForm, должен  обладать разделами initializa- tion и finalization, которые имеют следующий вид:

initialization

RegisterClass(TCF2Form);

finalization

UnRegisterClass(TCF2Form);

Обращение к процедуре RegisterClass() необходимо для создания класса,  про изводного от  TChildForm, доступного системе обработки  потоков главного прило жения при  загрузке  им своего  пакета.  Эта процедура аналогична процедуре Regis- terComponents(), которая делает  компоненты  доступными IDE  Delphi, а при  вы грузке пакета  необходимо обратиться к процедуре UnRegisterClass() для удаления зарегистрированного класса. Заметим, что процедура RegisterClass() делает  соот ветствующий класс доступным  только  главному  приложению. Причем главному  при ложению все еще остается неизвестным имя класса. Возникает вопрос: как же главное приложение создает  экземпляр класса,  имени которого оно не знает?  Это и является целью  данного упражнения — сделать  формы доступными главному  приложению без жесткого задания имен  классов  в исходном коде.  В листинге 14.2 представлен исход ный  код главной формы приложения, в котором особого внимания заслуживает реа лизация форм  с помощью  пакетов дополнений.

Листинг 14.2. Главная форма приложения, использующего пакеты дополнений

unit MainFrm;

interface uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,

Dialogs, StdCtrls, ExtCtrls, ChildFrm, Menus;const

{ Дочерняя форма регистрируется в системном реестре Windows. }

cAddInIniFile       = ‘AddIn.ini';

cCFRegSection       = ‘ChildForms'; // Раздел инициализации данных

FMainCaption        = ‘Delphi 6 Developer”s Guide Child Form Demo';

type

TChildFormClass = class of TChildForm; TMainForm = class(TForm)

pnlMain: TPanel;

Splitter1: TSplitter;

pnlParent: TPanel;

mmMain: TMainMenu;

mmiFile: TMenuItem;

mmiExit: TMenuItem;

mmiHelp: TMenuItem;

mmiForms: TMenuItem;

procedure mmiExitClick(Sender: TObject);

procedure FormCreate(Sender: TObject);

procedure FormDestroy(Sender: TObject);

private

// Ссылка на дочернюю форму.

FChildForm: TChildForm;

// Список доступных дочерних форм для построения меню.

FChildFormList: TStringList;

// Индекс меню Close Form (закрыть форму) по позиции

// смещения

FCloseFormIndex: Integer;

// Дескриптор загруженного в данный момент пакета.

FCurrentModuleHandle: HModule;

// Метод создания меню для доступных дочерних форм.

procedure CreateChildFormMenus;

// Обработчик загрузки дочерней формы и ее пакета.

procedure LoadChildFormOnClick(Sender: TObject);

// Обработчик выгрузки дочерней формы и ее пакета.

procedure CloseFormOnClick(Sender: TObject);

// Метод считывания имени потомка класса TChildForm.

function GetChildFormClassName(const AModuleName:

String): String;

public

{ Открытые объявления }

end;

var

MainForm: TMainForm;

implementation uses IniFiles;

{$R *.DFM}

function RemoveExt(const AFileName: String): String;

{ Вспомогательная функция удаления расширения из имени файла. }

begin

if Pos(‘.’, AFileName) <> 0 then

Result := Copy(AFileName, 1, Pos(‘.’, AFileName)-1)

else

Result := AFileName;

end;

procedure TMainForm.mmiExitClick(Sender: TObject);

begin

Close;

end;

procedure TMainForm.FormCreate(Sender: TObject);

begin

FChildFormList := TStringList.Create;

CreateChildFormMenus;

end;

procedure TMainForm.FormDestroy(Sender: TObject);

begin

FChildFormList.Free;

// Выгрузить все загруженные дочерние формы.

if FCurrentModuleHandle <> 0 then

CloseFormOnClick(nil);

end;

procedure TMainForm.CreateChildFormMenus;

{ Все доступные дочерние формы регистрируются в системном реестре

Windows. Здесь эта информация используется для создания элементов

меню, обеспечивающих загрузку всех дочерних форм. }

var

IniFile: TIniFile;

MenuItem: TMenuItem;

i: integer;

begin

inherited;

{ Считать список всех дочерних форм и построить меню на основании записей в системном реестре. }

IniFile := TIniFile.Create(ExtractFilePath(Application.ExeName)

+ cAddInIniFile);

try

IniFile.ReadSectionValues(cCFRegSection, FChildFormList);

finally

IniFile.Free;

end;

{ Добавить элементы меню для каждого модуля.

ОБРАТИТЕ ВНИМАНИЕ: свойство mmMain.AutoHotKeys должно быть

установлено равным значению maAutomatic. }

for i := 0 to FChildFormList.Count – 1 do beginMenuItem := TMenuItem.Create(mmMain); MenuItem.Caption := FChildFormList.Names[i]; MenuItem.OnClick := LoadChildFormOnClick; mmiForms.Add(MenuItem);

end;

// Создать разделитель

MenuItem := TMenuItem.Create(mmMain);

MenuItem.Caption := ‘-‘;

mmiForms.Add(MenuItem);

// Создать элемент меню Close Module (Закрыть модуль) MenuItem := TMenuItem.Create(mmMain);

MenuItem.Caption := ‘&Close Form'; MenuItem.OnClick := CloseFormOnClick; MenuItem.Enabled := False; mmiForms.Add(MenuItem);

{ Сохранить ссылку на индекс элемента меню, необходимый чтобы закрыть дочернюю форму. Она будет использована в другом методе. }

FCloseFormIndex := MenuItem.MenuIndex;

end;

procedure TMainForm.LoadChildFormOnClick(Sender: TObject);

var

ChildFormClassName: String;

ChildFormClass: TChildFormClass;

ChildFormName: String;

ChildFormPackage: String;

begin

// Заголовок меню представляет имя модуля.

ChildFormName := (Sender as TMenuItem).Caption;

// Получить реальное имя файла пакета.

ChildFormPackage := FChildFormList.Values[ChildFormName];

// Выгрузить все ранее загруженные пакеты.

if FCurrentModuleHandle <> 0 then

CloseFormOnClick(nil);

try

// Загрузить заданный пакет

FCurrentModuleHandle := LoadPackage(ChildFormPackage);

// Возвратить имя класса, необходимое для создания экземпляра

ChildFormClassName := GetChildFormClassName(ChildFormPackage);

{ Создать экземпляр класса с помощью процедуры FindClass().

Заметьте, этот класс должен быть уже зарегистрирован в

системе обработки потоков с помощью

процедуры RegisterClass(). Это реализовано в разделе

инициализации дочерней формы для каждого пакета дочерней

формы. }

ChildFormClass := TChildFormClass(

FindClass(ChildFormClassName));

FChildForm := ChildFormClass.Create(self, pnlParent);Caption := FChildForm.GetCaption;

{ Объединить меню дочерней формы с главным меню }

if FChildForm.GetMainMenu <> nil then

mmMain.Merge(FChildForm.GetMainMenu);

FChildForm.Show;

mmiForms[FCloseFormIndex].Enabled := True;

except

on E: Exception do begin

CloseFormOnClick(nil);

raise;

end;

end;

end;

function TMainForm.GetChildFormClassName(const AModuleName: String): String;

{ Имя реального потомка класса TChildForm находится в системном реестре. Данный метод считывает имя этого класса. }

var

IniFile: TIniFile;

begin

IniFile := TIniFile.Create(ExtractFilePath(Application.ExeName)

+ cAddInIniFile);

try

Result := IniFile.ReadString(RemoveExt(AModuleName),

‘ClassName’, EmptyStr);

finally

IniFile.Free;

end;

end;

procedure TMainForm.CloseFormOnClick(Sender: TObject);

begin

if FCurrentModuleHandle <> 0 then begin

if FChildForm <> nil then begin

FChildForm.Free;

FChildForm := nil;

end;

// Отмена регистрации всех классов модуля

UnRegisterModuleClasses(FCurrentModuleHandle);

// Выгрузка пакета дочерней формы

UnloadPackage(FCurrentModuleHandle);

FCurrentModuleHandle := 0; mmiForms[FCloseFormIndex].Enabled := False; Caption := FMainCaption;

end;

end;

end.Логика  этого  приложения довольно проста. В нем для определения доступных  па кетов   используется системный  реестр.  Имена   найденных  пакетов  помещаются  в строки меню,  создаваемого для предоставления возможности загрузки  каждого  паке та. Кроме того, считывается имя класса формы, содержащейся в каждом пакете.

Основную нагрузку несет  на себе  обработчик события LoadChildFormOnClick(). После  определения имени  пакета  этот метод загружает нужный пакет с помощью функ ции  LoadPackage(). (Функция LoadPackage() принципиально ничем  не отличается от функции LoadLibrary(), используемой для загрузки  библиотек DLL.)  Затем  этот метод определяет имя класса для формы, содержащейся в загруженном пакете.

Чтобы создать   класс,  необходима  ссылка  на  его  имя  (например TButton или TForm1). Но в этом главном  приложении отсутствует  жестко  заданное имя класса конкретной дочерней формы типа  TChildForm. Поэтому  имя класса  считывается из системного реестра. Главное  приложение способно передать имя  этого  класса  функ ции  FindClass() и получить от нее ссылку на заданный класс,  который уже был за регистрирован системой обработки потоков. Помните, что регистрация выполняется в разделе инициализации модуля  конкретной формы, который вызывается при  за грузке  соответствующего пакета.  Затем, с помощью следующих  строк, создается эк земпляр класса:

ChildFormClass := TChildFormClass(FindClass(ChildFormClassName)); FChildForm := ChildFormClass.Create(self, pnlParent);

НА ЗАМЕТКУ

Ссылка на класс (class reference) — это не более чем область в памяти, которая содержит информацию о классе. Это похоже на определение типов для класса. Когда класс регист- рируется потоковой системой VCL, вызов функции RegisterClass() загружает его в па- мять. Функция FindClass() находит в памяти область, занимаемую определением класса по его имени, и возвращает указатель на нее. Это не то же самое, что и экземпляр класса. Экземпляры класса обычно создаются при вызове функции конструктора класса (см. главу

2, “Язык программирования Object Pascal”).Переменная ChildFormClass представляет собой  заранее определенную ссылку на класс TChildForm и может содержать ссылку на любой потомок класса TChildForm.

Обработчик события CloseFormOnClick() просто закрывает дочернюю форму и выгружает ее пакет.  Остальная часть  кода используется в основном для создания ме ню пакетов и чтения информации из системного реестра.

Углубленное изучение данной технологии позволит создавать очень гибкие струк

туры приложений.

Источник: Тейксейра, Стив, Пачеко, Ксавье.   Borland Delphi 6. Руководство разработчика. : Пер.  с англ. — М. : Издательский дом “Вильямс”, 2002. —  1120 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии