InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Создание пакетных библиотек (пакеджей) в C++Builder 4


Нил Форд


Создание пользовательских компонент


Часть 1. Новые стандарты создания компонент


Создание приложений в C++Builder построена на его компонентно-ориентированной архитектуре. Другими словами, вся сложность функциональности скрыта внутри компонентов. Когда вам нужно получить доступ к базе данных, вы используете компоненты, вместо того, чтобы вызывать напрямую функции из библиотеки классов. Система C++Builder идеальна для создания повторно используемых "черных ящиков", инкапсулирующих в себе функциональность. C++Builder делает создание приложений очень легким делом, даже если вы ничего не знаете о том, как устроены компоненты и как их создавать.

Существует две группы программистов в C++Builder - пользователи компонентов и их создатели. Когда программисту нужно написать программу быстро, он использует компоненты. С другой стороны, чтобы реализовать новые решения, создаются новые компоненты. Компонентно-ориентированная раззработка открывает собой целую новую эру в программировании на C++.

При создании компонентов в C++Builder существует три уровня сложности. Первый (и самый легкий) - задание новых значений свойствам уже существующих компонент. Второй - когда вы расширяете новыми свойствами и методами функциональность компонентов-предков, от которых наследуете. И наконец, третий, самый сложный уровень предполагает создание компонент либо с нуля, либо почти с нуля(если вы почти полностью переписываете существующий компонент). В этой статье описывается только первый уровень.

Компоненты как классы


Каждый раз, когда компонент помещается на форму, C++Builder создает новый экземпляр его класса. Например, когда пользователь кладет на форму DBNavigator, C++Builder создает новый объект (с именем DBNavigator1) класса TDBNavigator. Чтобы создать новый компонентный класс, используется наследование, которое позволяет взять из класса-предка нужные методы и свойства, переопределить некоторые из них и добавить свои методы и свойства. Для доступа к компонентам используются специальные контейнеры. Эти контейнеры называются пакетными библиотеками (или пакеджами).

Создание пакеджа


В этом разделе описано создание пакеджа для нового компонента. Если вы уже знакомы с архитектурой и методами разработки пакеджей в C++Builder, то можете его пропустить.

Пакеджи в C++Builder - контейнеры для компонент. Они представляют собой DLLs особого вида с расширением .BPL. Пакедж позволяет создавать экземпляры содержащихся в нем объектов - это главное его отличие от обычной DLL. Все стандартные компоненты C++Builder уже находятся в пакеджах. В C++Builder также существует разница между пакеджами дизайна(design-time packages), которые используются только внутри IDE, и пакеджами выполнения(run-time packages), которые необходимо поставлять вместе с приложением, если оно динамически использует их код. Такое решение также позволяет разделять код пакеджей между разными приложениями C++Builder на одной машине и создает другие удобства. Подробно это описано в справке и документации.

Чтобы создать новый пакедж, выберите File | New из главного меню и выберите Package. Появится окно редактора пакеджей. Новый пакедж будет назван по умолчанию Package. Это окно показано на рис. 1. Как и при создании любого нового проекта, измените имена и пути, заданные по умолчанию на те, которые нужны вам. Поскольку пакеджи - это DLL, то они подчиняются тем же правилам. То есть они должны быть на диске там, где ваше приложение или IDE сможет их найти и загрузить. Так что имеет смысл создать для ваших пакеджей отдельную папку, которую затем указать во всех путях и установках в различных конфигурационных файлах. В протвоположном же случае, если прописать там все папки, где разбросаны ваши пакеджи, это может замедлить время поиска всех DLL.

Рис. 1. Редактор пакеджей

Дополнение переводчика: - в редакторе пакеджей вы можете видеть две секции - Contains и Requires. В секции Contains находится содержимое проекта - модули с исходным текстом компонент на C++ или Object Pascal, различные библиотеки статически подключаемые к проекту и библиотеки импорта от DLL(.lib-файлы) и файлы ресурсов. В секции Requires перечислены ссылки на внешние библиотеки (в основном, пакеджи C++Builder - для каждого из них в этой секции должен быть указан соответствующий bpi-файл), которые необходимы для работы модулям из предыдущей секции.

После того, как пакедж создан и назван, в него можно добавлять новые компоненты(модули). Далее в статье на примере создания нового компонента описано, как выборочно переназначить значения по умолчанию свойств существующего компонента.

Создание нового компонента

Наверное, самый простой тип компонент, который можно создать в C++Builder - это тот, который просто задает новые значения по умолчанию для некоторых свойств своего предка. Часто программисты проделывают одни и те же операции, когда они кладут на форму компонент и меняют каждый раз один и тот же набор свойств, задавая те же самые значения. В этой ситуации было бы удобно просто переопределить значения свойств по умолчанию в новом компоненте.

Редактор пакеджей позволяет вам либо создать новый компонент, либо добавить уже существующий. Нажав на кнопку Add в редакторе, можно вызвать диалог, показанный на рис.2. Он позволяет вам создать новый компонент, добавить существующий и импортировать ActiveX. Для этой статьи мы выберем создать новый(New Component).

Рис. 2. Редактор пакеджей позволяет добавить существующий компонент.

Страница New Component показана на рис.3. Этот компонент назван нами TDefaultedDBNavigator. TDBNavigator всегда имеет по умолчанию все кнопки, а также выключенную опцию ToolTips. В этом примере он изменен так, чтобв показывались только четыре кнопки и всегда показывались подсказки ToolTips. Новый компонент будет добавлен на страницу палитры с именем Informant(можно создавать новые страницы, задав новые имена). В поле unit file name задается путь имя и путь к файлам с исходным текстом.

Рис. 3. Страница New Component.

В C++Builder сочетание заголовочного файла и файла с исходным кодом называется модуль("unit"). Также в C++Builder принято добавлять букву "T" перед именем класса. Это осталось от Object Pascal, который нечувствителен к регистру символов. Так предполагалось отличать имена классов от имен переменных.

Мастер создает скелет исходных текстов компонента. Заголовочный и основной файлы показаны ниже:

//DefaultedDBNavigator.h
// --------------------------------------------------------

#ifndef DefaultedDBNavigatorH

#define DefaultedDBNavigatorH

// --------------------------------------------------------

#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <DBCtrls.hpp>
#include <ExtCtrls.hpp>

// --------------------------------------------------------

class PACKAGE TDefaultedDBNavigator : public TDBNavigator

{

  private:

  protected:

  public:

     __fastcall TDefaultedDBNavigator(TComponent* Owner);

    __published:

};

// --------------------------------------------------------

#endif

  

  
//DefaultedDBNavigator.cpp
// --------------------------------------------------------

#include  

#pragma hdrstop

  

#include "DefaultedDBNavigator.h" 

#pragma package(smart_init) 

// --------------------------------------------------------

// ValidCtrCheck is used to assure that the components

// created don't have any pure virtual functions. 

  

static inline void ValidCtrCheck(TDefaultedDBNavigator *)

{

   new TDefaultedDBNavigator(NULL);

}

// --------------------------------------------------------

__fastcall TDefaultedDBNavigator::TDefaultedDBNavigator(

  TComponent* Owner) : TDBNavigator(Owner)

{

}

// --------------------------------------------------------

namespace Defaulteddbnavigator

{

   void __fastcall PACKAGE Register()

  {

    TComponentClass classes[1] =

      {__classid(TDefaultedDBNavigator)};

    RegisterComponents("Informant", classes, 0);

  }

}

// --------------------------------------------------------

Как уже было сказано, компоненты - это классы, порожденные от уже существующих. Чтобы быть компонентом, класс дожен наследовать как минимум от TComponent. Это класс, который инкапсулирует общие для всех компонент свойства - способность переноситься на форму во время проектирования, иметь свойства, котрые видны в Object Inspector, и возможность быть зарегистрированным в палитре компонент. Наш TDefaultedDBNavigator наследует от встроенного TDBNavigator, который также является потомком TComponent. Обратите внимание на модификатор PACKAGE перед объявлением класса. Он обеспечивает создание экземпляров класса из пакеджа. В объявлении класса пока присутствует только один метод - конструктор. Наш компонент - простой класс, поэтому не нужно добавлять чего-то еще. В главном файле исходного кода включаются несколько необходимых заголовочных файлов. Также тут присутствует метод ValidCtrCheck. Эта функция автоматически включается в каждый модуль нового компонента. Она позволяет пользователю компонента определить, не содержит ли класс компонента чистых виртуальных функций, то есть может ли быть корректно создан экземпляр компонента. Она проверяет это путем создания локального экземпляра компонента и сразу же его уничтожает. В случае, если экземпляр компонента не может быть успешно создан, возбуждается исключение, показывающее, что некоторые необходимые методы в классе не реализованы.

Также в этом файле мы видим пока пустой конструктор. Он включает в свой список инициализации членов вызов конструктора предка. Это сделано для удобства. Одна из классических ошибок в создании компонент в Delphi/C++Builder - забыть вызвать конструктор предка, который, в свою очередь, вызывает по цепочке конструкторы своих предков. Этот вызов никогда нельзя удалять.

Примечание переводчика: Все дело в том, что VCL написана на Object Pascal, а в нем, в отличие от C++, при создании экземпляра потомка автоматически вызывается только конструктор потомка, а конструкторы предков автоматически НЕ вызываются, так что для корректной работы их необходимо вызвать вручную. Таким образом получается, что в Object Pascal(и, соответственно, внутри VCL) порядок вызова конструкторов обратен тому, что принят в C++.

Последняя функция в этом файле - Register, которая вызывается внутри специального пространства имен. Эта функция регистрирует компонент(ы) из данного модуля(пакеджа) в палитре. Имя страницы палитры, заданное в мастере, содержится здесь и здесь его можно поменять.

Чтобы переопределить значения свойств по умолчанию, надо просто задать для них новые значения в конструкторе. Итак, чтобы создать наш новый навигатор из старого, нужно добавить в конструктор три линии кода. Модифицированный конструктор показан ниже. Сначала очищается множество(set) кнопок навигатора и в него добавляются новые элементы, и наконец, свойство ShowHint устанавливается в true.

__fastcall TDefaultedDBNavigator::TDefaultedDBNavigator(TComponent* Owner):
	 TDBNavigator(Owner)
{

  VisibleButtons.Clear();
  VisibleButtons = VisibleButtons << nbFirst << nbPrior << nbNext << nbLast;
  ShowHint = true;
}

Чтобы инсталлировать новый компонент, надо откомпилировать пакедж. Это можно сделать с помощью редактора пакеджей. Как и в любом другом проекте, он автоматически перекомпилирует все измененные файлы проекта. После удачной компиляции компоненты, содержащиеся в пакедже, можно инсталлировать в палитру с помощью кнопки Install. В данном случае будет создана новая страница (Informant) и на ней появится готовый к использованию компонент TDefaultedDBNavigator.

"Новые" Характеристики компонента


Когда компонент помещен на форму, появляются только кнопки навигации и свойство ShowHint по умолчанию установлено в true. Новый компонент показан на рис.6.

Рис. 6. Новый компонент.

Однако, что же произойдет, если пользователь сменит одно из этих свойств? Можно ли это проделать во время дизайна или выполнения? Да, можно. Пользователь также может изменять эти значения в Object Inspector и они корректно будут себя вести во время выполнения. Как же так получилось? Разве значения этих свойств не задаются в конструкторе раз и навсегда? Ответ к этой головоломке кроется в механизме, который C++Builder использует для построения форм. Этот пример слегка упрощен, но достаточно точен для иллюстрации механизма.

Сначала C++Builder вызывает конструктор формы. В этот момент он открывает DFM файл(который в это время уже встроен в выполняемый), считывает имена компонентов и конструирует их один за другим. При этом, однако, он игнорирует значения свойств из DFM файла. Порядок, в котором конструируются компоненты, такой же, как они идут в DFM файле. Дальше, как только конструктор формы завершается, происходит возврат к DFM и начинается присваивание значений всем свойствам компонент.

Заметьте, что C++Builder всегда конструирует компоненты со значениями по умолчанию, и только потом присваивает значения из DFM. Это также объясняет, почему наш компонент работает правильно - даже если в конструкторе задаются новые значения, они будут потом перекрыты значениями с формы.

Эта техника также работает с компонентами баз данных. Например, если каждый новый проект использует одни и те же установки соединения с БД, было бы легко и удобно создать новый измененный компонент Database с уже установленными характеристиками. Это также верно для таблиц или запросов, которые тоже часто используются повторно.

Возможно также воспользоваться еще несколькими преимуществами такой архитектуры. Наприемр, можно перопределить метод Loaded компонента. Форма автоматически вызывает его, когда заканчивает загрузку и присваивание данных из DFM. Это означает, что в нем можно предпринять некоторые дополнительные действия.

Когда компонент инсталлируется, для него назначается иконка(битмап) его ближайшего предка или битмап по умолчанию. Можно создать оригинальный битмап для компонента.

Битмапы компонентов


Битмапы, которые видны на палитре, не компилируются в компонент, так как они нужны только во время проектирования. Вместо этого они должны быть в файлах с расширением .DCR (Dynamic Component Resource) и именем, совпадающим с именем модуля компонента. Это требование необходимо соблюдать - если имя будет отличаться, битмап не загрузится. C++Builder предоставляет средство Image Editor для создания битмапов (см. рис.7). С помощью него можно создать новый DCR файл.

Рис. 7. Image Editor.

Чтобы создать новый битмап для компонента, разработчик должен щелкнуть правой клавишей и выбрать соответствующий пункт меню. Появится диалог Bitmap Properties, показанный на рис. 8. Битмап должен быть квадратом 24x24 пикселя и иметь палитру VGA (16 colors).

Рис. 8. Диалог Bitmap Properties.

Чтобы начать редактирование битмапа, нужно выбрать соответствующий пункт меню. Появится палитра редактирования битмапа, показанная на рис.9.

Рис. 9. Палитра редактирования растровых изображений.

Если компонент уже был инсталлировани в палитру, его надо убрать оттуда и повторно инсталлировать, чтобы заставить C++Builder включить DCR-файл в пакедж. После того, как битмап был правильно инсталлирован, он появится вместе с компонентом в редакторе пакеджей. Компонент TDefaultedDBNavigator вместе со своим битмапом показан на рис.10.

Рис. 10. TDefaultedDBNavigator и его битмап.

Отладка пакеджей


C++Builder позволяет отлаживать и пакеджи, и DLL. На самом деле, процесс их отладки одинаков, так как в общей части они одно и то же.

В Win32, все DLLs выполняются в адресном пространстве загружающего их процесса. Чтобы начать отладку, необходимо сначала запустить этот процесс, который называется хост-процессом. C++Builder IDE позволяет отлаживать DLL, но прежде чем начать отладку, вы должны указать имя своего хост-процесса. Вы можете это сделать, открыв проект DLL из IDE. После этого выберите Run | Parameters из главного меню. C++Builder отобразит даилог, показанный на рис. 11. После введения имени хост-процесса вы можете, выбрав Run |Run или нажав F9, запустить его. Когда программа доходит до точки останова в коде вашей DLL, C++Builder отобразит редактор кода на месте вашей точки останова.


Рис. 11. Задание хост-процесса.

Использование C++Builder IDE в качестве хост-процесса для пакеджа


Так как пакедж - это DLL, вы можете отлаживать его точно так же. Вы открываете проект пакеджа в C++Builder IDE, выбираете Run | Parameters из меню и задаете имя хост-процесса. А теперь простой трюк - если вы хотите отлаживать пакедж дизайна, просто задайте BCB.EXE как хост-процесс, вот и все. Задайте полный путь к BCB.EXE и его имя в поле Host Application.

После того, как вы зададите BCB.EXE как хост-процесс, выберите Run | Run или нажмите F9 для начала отладки пакеджа. C++Builder создаст другой экземпляр C++Builder, который выступает в качестве запускающего процесса. Эта вторая копия и загрузит ваш пакедж дизайна. Вернитесь в первую копию, расставьте точки останова. Затем снова перейдите во вторую копию и манипулируйте ею, чтобы достичь этих точек.

Замечания и предложения


Замечание 1: Не пытайтесь запустить программу из второй копии C++Builder. Другими словами, не нажимайте F9, находясь в ней. Вы можете отлаживать только одну программу за раз, и не стоит пытаться создавать несколько копий среды - это только породит хаос. Лучше вообще не нажимать F9, а пользоваться только кнопками панели инструментов и меню. Ведь очень легко потерять ту нить, которая позволит программе получить фокус ввода.

Замечание 2: Во время отладки может появиться исключение EResNotFound, сообщающее "Resource DBXDBI not found.". Источник его до конца не ясен, но его можно просто игнорировать.

Замечание 3:Сохраняйтесь, прежде чем начать отлаживаться :))

Замечание 4: Эта техника прекрасно работает с пакеджами, котроые подключаются к IDE с помощью OpenTools IDE.

Замечание 5: Постарайтесь отлаживаться в Windows NT, если это возможно. Если что-нибудь упадет, так будет гораздо легче восстановиться.

Замечание 6: Вы можете указать параметр командной строки -ns для второй копии C++Builder, чтобы при ее запуске не показывалась картинка.

Замечание 7: Компиляция проекта из второй копии C++Builder может вообще не работать, так как она, скорее всего, запускается из папки, где находится проект пакеджа.

Проект к этой статье можно загрузить здесь.


Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua