5 - 2006

ИНТЕРМЕХ изнутри. IMBASE

Николай Кожемякин

Импортирование заголовков интерфейсов

Подключение к IMBASE

Application -> Catalogs

Application -> Catalogs -> Catalog -> Folders

Application -> Catalogs -> Catalog -> Folders -> Folder

Application -> Catalogs -> Catalog -> Folders -> Folder -> Record

В предыдущих статьях, посвященных системе управления базой данных стандартных изделий, материалов и пользовательских данных IMBASE, используемой в системах автоматизированного проектирования и ведения документации ИНТЕРМЕХ, было рассказано о применении IMBASE в связке с другими программами ИНТЕРМЕХ. В настоящей статье речь пойдет о внутреннем устройстве информационной базы данных и о ее функциональных возможностях по связи с внешними программами сторонних разработчиков.

Компания ИНТЕРМЕХ всегда делала упор на возможности расширения и адаптации своих программных продуктов для различных категорий пользователей — от небольшого конструкторского бюро, где трудится всего несколько конструкторов, до корпоративных заказчиков, у которых количество одновременно работающих пользователей (конструкторов, технологов, работников ОТД и др.) исчисляется сотнями. В таких условиях от системы требуется не только наличие заявленных производителем функциональных возможностей, но и весьма специфические требования, количество которых прямо пропорционально числу установленных на предприятии рабочих мест.

Для обеспечения возможности использовать функциональность IMBASE для решения специфических задач наших клиентов силами их программистов система предлагает набор функций, известный как API. Программный интерфейс, необходимый для связи IMBASE с собственными разработками заказчиков, находится внутри исполняемого модуля, файла Imbase.exe. Простой, но в достаточной мере иллюстрирующий возможности API IMBASE пример приводится для рассмотрения в настоящей статье. В этом примере в качестве среды разработки будет использоваться очень популярный у отечественных разработчиков Borland Delphi, но все нижесказанное относится и к C++Builder той же фирмы, и к Microsoft Visual Studio.

Импортирование заголовков интерфейсов

Перед тем как начать работать с функциями API, надо дать возможность среде разработки узнать о существовании этих функций. Как уже было отмечено, вся эта информация содержится внутри исполняемого файла. Для этого IMBASE должна быть зарегистрирована в операционной системе вашего компьютера в качестве сервера COM. Только после этого можно начинать импорт заголовков интерфейсов. Итак, начнем. Запускаем Delphi, создаем новый проект и импортируем описание функций IMBASE. Для этого в меню Project выбираем Import Type Library… (рис. 1).

Рис. 1. Добавление в проект библиотеки типов IMBASE

Рис. 1. Добавление в проект библиотеки типов IMBASE

В появившемся диалоговом окне вы увидите список COM-серверов, доступных для импорта и использования в вашем проекте, но нас интересует IMBASE, поэтому в предложенном списке попробуем ее найти. Мы ищем строчку вида ImBase Library(Version 1.0) (рис. 2).

Рис. 2. Выбор библиотеки типов IMBASE

Рис. 2. Выбор библиотеки типов IMBASE

Если такая строчка найдена, нажимаем кнопку CreateUint, и среда разработки сгенерирует на базе информации, взятой из IMBASE, программный код, необходимый для использования API, и поместит ссылку на него в исходный текст вашего проекта. Если же строчки с COM-сервером вы в списке не нашли, значит IMBASE еще ни разу не была запущена и вам надо это сделать, после чего повторить попытку создания заголовков.

В исходном коде вашего проекта должны появиться строчки, похожие на эти:

program Project1;

uses

Forms,

Unit1 in ‘Unit1.pas’ {Form1},

ImBase_TLB in ‘..\Imports\ImBase_TLB.pas’;

 

После таких нехитрых подготовительных операций можно приступать к написанию кода, который будет использовать API IMBASE, а заодно и предоставлять пользователю информацию о содержимом базы данных.

В начало В начало

Подключение к IMBASE

Первым шагом перед использованием в вашей программе функций API является активизация сервера приложений, которым в нашем случае является IMBASE. Код, который выполняет активизацию сервера и ожидает вход в систему, оформим в виде процедуры и назовем его CheckImbaseConnection. Выполнять этот код удобнее всего автоматически при отображении главной формы нашего приложения. Для этого подпишемся на событие OnShow главной формы:

procedure TForm1.FormShow(Sender: TObject);

begin

      CheckImbaseConnection;

      BuildMainNodes;

end;

var

Form1: TForm1;

ImBase : IImbaseApplication;

//——————————————————————————————-

procedure CheckImbaseConnection;

var

      Status:ImBaseLoadStatus;

begin

      if( Imbase = nil) then

      begin

       ImBase := CoImbaseApplication.Create;

       while true do

             begin

             Status := ImBase.Status;

             Application.ProcessMessages;

             if Status = IST_READY then Break;

             end;

      end;

end;

В теле главной формы объявлена глобальная переменная ImBase типа IimbaseApplication. Эта переменная является экземпляром COM-сервера IMBASE. Строка кода ImBase := CoImbaseApplication.Create; создает в памяти COM-объект и запускает приложение imbase.exe.

После запуска IMBASE просит пользователя выполнить регистрацию путем ввода имени и пароля (рис. 3).

Рис. 3. Окно ввода имени пользователя и пароля

Рис. 3. Окно ввода имени пользователя и пароля

Наш код входит в цикл ожидания этих действий и проверяет состояние системы путем проверки значения, которое возвращается в методе Status до тех пор, пока не станет равным значению константы IST_READY, описанной в файле Imbase_tlb.pas, который был получен на этапе импорта заголовков интерфейсов.

После удачного подключения начинаем использовать полученный интерфейс на сервер IimbaseApplication и строим корневые папки иерархии Каталогов и Справочников. Для этого расположим на форме элемент управления типа TTreeView и назовем его просто: treeView1:

procedure TForm1.FormShow(Sender: TObject);

begin

      CheckImbaseConnection;

      BuildMainNodes;

end;

//——————————————————————————————-

procedure TForm1.BuildMainNodes;

var

      RootNode : TTreeNode;

begin

      RootNode := TreeView1.Items.GetFirstNode;

      RootNode.Data := Pointer(Imbase);

      RootNode.HasChildren := true;

      RootNode.ImageIndex := iiRoot;

      RootNode.SelectedIndex := iiRoot;

end;

Код в процедуре BuildMainNodes просто инициализирует главный узел дерева и подготавливает его к интерактивной работе с пользователем. Важным моментом здесь является то, что узлу устанавливается свойство HasChildren в true. Эта техника применяется в дальнейшем (при формировании узлов Каталогов и папок) для того, чтобы отобразить символ «+» возле названия узла, но не формировать его содержимое сейчас, потому что, возможно, пользователь и не захочет дальше раскрывать этот узел. Таким образом экономятся память и время.

Главная форма обрабатывает события, которые возникают от раскрытия узла дерева. При этом анализируется, какой узел раскрывается на основании данных, сохраненных в свойстве Data этого узла. В нашем примере там хранится интерфейс отображаемого элемента.

При нажатии пользователем на символ «+» у узла возникает вышеописанное событие. В процедуре обработки проверяются данные в поле Data выбранного узла, и в зависимости от того, интерфейс какого объекта там находится, выполняется соответствующая обработка.

При раскрытии главного узла, в котором хранится интерфейс IimbaseApplication, выполняется сканирование всех каталогов:

procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;

var AllowExpansion: Boolean);

var

      NodeItf,Unk : IInterface;

begin

      NodeItf := IInterface(Node.Data);

      AllowExpansion := true;

      if(Node.HasChildren and (Node.Count = 0)) then // проверка на неисследованный узел

      begin

             try

                   TreeView1.Items.BeginUpdate;

                   Node.HasChildren := false;

                   if( NodeItf <> nil) then

                   begin

// Главный узел

                         if NodeItf.QueryInterface (IID_IImbaseApplication,Unk) = S_OK

                         then PopulateCatalogs(Node, Unk As IImbaseApplication)

                         else

// Узел Каталога

                         if NodeItf.QueryInterface (IID_IImbaseCatalog,Unk) = S_OK

                         then PopulateCatalog(Node, Unk As IImbaseCatalog)

                         else

// Узел папки      

                         if NodeItf.QueryInterface (IID_IImbaseFolder,Unk) = S_OK

                         then PopulateFolder(Node, Unk As IImbaseFolder);

                   end;

             finally

                   TreeView1.Items.EndUpdate;

             end;

      end;

end;

 

Интерфейс IImbaseApplication, который мы получили в результате подключения к COM-серверу, является отправной точкой иерархической модели объектно-ориентированного API IMBASE. Через этот интерфейс можно получить все остальные объекты, реализованные через API.

Как видно из рис. 4, интерфейсы бывают двух типов:

Рис. 4. Иерархия API IMBASE

Рис. 4. Иерархия API IMBASE

•  интерфейс, который связан с объектом базы данных (Каталог, папка или таблица);

•  интерфейс, содержащий коллекцию объектов базы данных.

В нашем примере мы подробно рассмотрим ветку Application -> Catalogs -> Catalog -> Folders -> Folder -> Record -> Table.

В начало В начало

Application -> Catalogs

В IMBASE каталоги бывают трех логических типов:

•  собственно Каталоги;

•  Справочники;

•  Технологические справочники.

Для их группировки создадим в дереве три узла с соответствующими именами и поместим в них Каталоги:

//——————————————————————————————-

procedure TForm1.PopulateCatalogs(Root: TTreeNode;Itf: IImbaseApplication);

var

      Nodes: TTreeNodes;

      CatNode,RefNode,TechNode,MainNode,Node : TTreeNode;

      Catalogs: IImbaseCatalogs;

      Catalog : IImbaseCatalog;

      Count,Index  : integer;

      ImageIndex: integer;

begin

      Nodes := Root.Owner;

      CatNode := Nodes.AddChild(Root,’Каталоги’);

      RefNode := Nodes.AddChild(Root,’Справоч­ники’);

      TechNode:= Nodes.AddChild(Root,’Техно­ло­гические справочники’);

      Catalogs :=Itf.Catalogs; // Получаем интерфейс на список Каталогов системы

      Catalogs._AddRef;

      CatNode.Data := Pointer(Catalogs);

      RefNode.Data := Pointer(Catalogs);

      TechNode.Data := Pointer(Catalogs);

      Count := Catalogs.Count; //Получаем количество Каталогов

      for Index := 0 to Count-1 do // Цикл по всем каталогам

             begin

             Catalog := Catalogs.Item(Index); // Получаем интерфейс на Каталог

             case Catalog.Type_ of // Определяем корень для каталога

              ICT_CATALOG:

                   begin

                   MainNode := CatNode;

                   ImageIndex := iiCatalog;

                   end;

              ICT_REF:

                   begin

                   MainNode := RefNode;

                   ImageIndex := iiRef;

                   end;

              ICT_TECHREF:

                   begin

                   MainNode := TechNode;

                   ImageIndex := iiRef;

                   end;

             end;

             // создаем узел

             Node := Nodes.AddChild(MainNode,Ca­talog.Name);

             Node.Data := Pointer(Catalog);

             Node.HasChildren:= true;

      end;

end;

Код этой процедуры очень простой, но в ней выполняются два важных действия:

•  получаем у главного интерфейса список всех каталогов системы через свойство Catalogs;

•  создаем для каждого из них свой узел в главном дереве и сохраняем в поле Data интерфейс Каталога IImbaseCatalog.

После завершения процедуры PopulateCatalogs наше дерево приобретет вид, показанный на рис. 5.

Рис. 5. Результат раскрытия узла Каталогов

Рис. 5. Результат раскрытия узла Каталогов

В начало В начало

Application -> Catalogs -> Catalog -> Folders

Далее, при нажатии на символ «+» узла каталога программа снова попадает в метод обработки раскрытия узла. Поскольку в поле Data узла содержится ссылка на объект IimbaseCatalog, вызывается процедура формирования папок верхнего уровня каталога. Код этой процедуры получает у Каталога коллекцию папок верх­не­го уровня и добавляет соответствующие узлы в узел Каталога:

procedure TForm1.PopulateCatalog(Root: TTreeNode; Itf: IImbaseCatalog);

var

      Nodes: TTreeNodes;

      Node : TTreeNode;

      Index,Count : integer;

      Folders: IImbaseFolders;

      Folder : IImbaseFolder;

begin

      Nodes := Root.Owner;

      Folders := Itf.Folders; // получаем у Каталога коллекцию корневых папок

      Count := Folders.Count;

      for Index :=0 to Count-1 do // для каждой папки в коллекции создаем подузел

             begin

             Folder := Folders.Item(Index);

             Node := Nodes.AddChild(Root,Folder.Name);

             Node.ImageIndex := iiFolder;

             Node.SelectedIndex := iiFolderOp;

             Folder._AddRef;

             Node.Data := Pointer(Folder); // сохраняем ссылку на интерфейс папки

             Node.HasChildren := (Folder.SubFoldersCount > 0); // проверяем наличие подпапок

             end;

end;

Основное отличие кода этой процедуры состоит в том, что у папки запрашивается количество ее подпапок через свойство SubFoldersCount и символ «+» в дереве изображается только в том случае, если они есть. Результат выполнения этой процедуры показан на рис. 6.

Рис. 6. Результат раскрытия корневых папок Каталога

Рис. 6. Результат раскрытия корневых папок Каталога

В начало В начало

Application -> Catalogs -> Catalog -> Folders -> Folder

И наконец, последний штрих в построении дерева иерархии справочной базы — раскрытие содержимого папки IMBASE:

procedure TForm1.PopulateFolder(Root: TTreeNode; Itf: IImbaseFolder);

end;

Здесь отличия в коде от процедуры PopulateCatalog минимальны и заключаются в том, что в качестве аргумента в процедуру передается не интерфейс Каталога, а интерфейс папки. Результат работы процедуры представлен на рис. 7.

Рис. 7. Результат раскрытия папок

Рис. 7. Результат раскрытия папок

Как видно из приведенных примеров, по­строение иерархии не является сложной задачей и может быть выполнено очень быстро и с минимальным применением пользовательского кода.

В начало В начало

Application -> Catalogs -> Catalog -> Folders -> Folder -> Record

Кроме коллекции вложенных папок, каждая папка IMBASE содержит коллекцию записей Каталога (IImbaseRecords), которая доступна через свойство Records. Эта коллекция представляет собой одну или несколько записей Каталога, которые, в свою очередь, могут содержать ссылку непосредственно на таблицу с данными. Содержится или нет в записи Каталога ссылка на таблицу — определяется при создании Каталога администратором системы.

В нашем примере в правой части окна содержится элемент управления типа TListView, в котором отображается содержимое выбранного в дереве узла папки.

При выборе узла с папкой вызывается метод, который получает коллекцию записей папки и выводит ее в виде списка. Фрагмент кода, который это реализует, приведен ниже:

Records := Folder.Records;

Count := Records.Count;

for Index:=0 to Count-1 do

begin

      Item := Items.Add;

      Record_ := Records.Item(Index);

      Table := nil;

      try

             Table := Record_.Table;

      except

      end;

      if( Table <> nil) then begin

             Table.Bind;

             Item.Caption := Table.Name;

             Item.SubItems.Add(Таблица Imbase ‘);

             Item.ImageIndex := iiTable;

             Table._AddRef;

             Item.Data := Pointer(Table);

      end

      else begin

             Item.Caption := Record_.Values[0];

             Item.SubItems.Add(‘ Запись в Ка­та­логе ‘);

             Item.ImageIndex := iiRecord;

      end;

end;

Результат работы этого фрагмента кода показан на рис. 8.

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

Рис. 8. Построение списка таблицы папки

Рис. 8. Построение списка таблицы папки

В завершение хочется еще раз напомнить читателям об открытости и адаптивности комплекса систем ИНТЕРМЕХ. Совместными усилиями мы сможем построить единую информационную среду предприятия, объединив уже используемые на предприятии системы автоматизации с решениями ИНТЕРМЕХ.

В начало В начало

САПР и графика 5`2006