VpPuZzle. Обзор. Ч.1

Материал из wikiru.visual-prolog.com

Автор: Виктор Юхтенко

VpPuZzle. Обзор

Компонентная технология, базирующаяся на DLL (именуемая как Visual Prolog Puzzle или VpPzl или pzl-технология или просто PZL) является набором соглашений для построения приложений на базе VIP с использованием пакетов, помещенных в DLL.

Пакет VIP, удовлетворяющий соглашениям PZL-технологии называется pzl-Компонентой (pzlComponent). VIP-проект (генерирующий исполняемое приложение или DLL), содержащий pzl-Компоненты и организованный специальным образом, называется pzl-Контейнером (pzlContainer). Ниже приводится краткое описание концепции pzl-технологии и ее основных механизмов.

Цели

Говоря о компонентах, мы имеем в виду, что компонента - это часть программы, которая может быть повторно использована без изменения представляющего ее исходного текста.

Программирование на основе компонент является одним из способов повышения эффективности процесса разработки программного обеспечения. В технологиях, ориентированных на применение компонент, компоненты обычно представляются одним из следующих способов:

  • Исходный текст
  • Статически связываемые библиотеки (статические библиотеки)
  • Динамически связываемые библиотеки (DLL)

Применительно к VIP примером представления компоненты в виде исходного текста является пакет.

В VIP возможно также применение статически связываемых библиотек, но полностью этот процесс не поддержан инструментальными средствами самой системы программирования. Единственным преимуществом, которое дают в сравнении с использованием исходного кода в виде компонент статически связываемые библиотеки, является возможность скрыть внутренние алгоритмы реализации.

Компоненты, представленные как исходными текстами, так и статическими библиотеками, имеют недостатки:

  • Размер приложения является суммой размеров используемых компонент
  • Расширение функциональных свойств приложения возможно только путем его перестройки самими разработчиками

Наилучшим способом представления компонент является применение DLL. С использованием DLL достигаются практически все цели эффетивной разработки. Приложения становятся расширяемыми и легко модифицируемыми.

Эффективность использования DLL существенным образом повышается, если взаимодействие с компонентами производится на основе выработанных соглашений. Одним из возможных наборов соглашений по использованию DLL является MSCOM ( Microsoft Component Object Model) . Соглашения, используемые фирмой Microsoft, сделали применение DLL в качестве компонентной модели широко используемым.

Система программирования Visual Prolog (VIP) поддерживает использование компонентов, удовлетворяющих соглашениями MSCOM. Использование таких компонент требует в некоторых случаях определенного уровня квалификации, а иногда и изворотливости.

Помещая код в DLL, программист встречается с необходимостью:

  • беспокоиться о загрузке DLL в память и обратном освобождении памяти после завершения использования DLL
  • органиизации единого пространства обработки исключительных ситуаций
  • беспокоиться о едином потоке вывода для всех компонент
  • обеспечить прозрачные правила взаимодействия компонент между собой

PZL-технология направлена на решение всех перечисленных задач и предоставление пользователю возможностей использования DLL без необходимости знания технических деталей этих носителей кода. Кроме того, PZL-технология позволяет пользоваться имеющейся средой программирования (IDE).

Обычный способ использования DLL

Предположим, у нас есть два взаимодействующих класса

Класс A Класс B
inrerface a 
predicates 
  pA:(). 
end interface a 
 
class a:a 
end class a 
 
implement a 
clauses 
  ...
  Ob=b::new(), 
  Ob:pB(). 
clauses 
  pA():- 
  ... 
end implement a
 
inrerface b 
predicates 
  pB:(). 
end interface b 
 
class b:b 
end class b 
 
implement b 
clauses 
  ... 
  Oa=a::new(), 
  Oa:pA(). 
clauses 
  pB():- 
  ... 
end implement b


Представим теперь, что по каким-либо причинам мы решили поместить код класса B в DLL. Стандартные методы MS Windows достаточно хорошо поддержаны в классе pfc\application\useDLL. Процедура сравнительно простая: необходимо с помощью IDE создать проект, генерирующий в качестве целевого исполняемого кода DLL. Затем в этот проект должен быть включен пакет, который включает класс B. Необходимо создать экспорт предиката и он должен быть связан с предикатами класса B.

Фрагмент кода, который использует предикат, помещенный в DLL, должен быть также преобразован. В частности вызов предиката pB из класса A, будет выглядеть следующим образом:

...
ObjDll = useDll::load(DLLFileName), 
pB_Ref=ObjDll:getPredicateHandle(pB_Exp), 
pB_Ref(), 
...

Практическая ценность кода, приведенного выше, не так важна. Главное, на что следут обратить внимание, - это то, что код класса A, вызывающий предикат B, должен быть изменен существенным образом и он становится жестко зависящим от процедуры взаимодействия с классом, который помещен в DLL.

Более того, создание экземпляра класса A и вызов предиката pA со стороны класса B потребует создания специального кода.

VPPuZzle: Основная идея метода

PZL-технология позволяет производить разделение приложения на части, помещаемые в DLL, таким образом, что исходный код классов не претерпевает существенных изменений. При этом как вызывающий, так и вызываемый классы безразличны к месту своего размещения. То есть одинаковым образом работают схемы размещения вызывающего и вызываемого классов:

  • Главное приложение - DLL
  • DLL - Главное приложение
  • DLL - DLL

Это возможно в результате использования двух замечательных свойств VIP:

  • Главное приложение и DLL, создаваемые VIP, работают в одном адресном пространстве памяти
  • Все экземпляры классов принадлежат одному домену Object.

PZL-технология строится вокруг основной идеи: вызов конструктора new(...) транспортируется в DLL, в DLL создается экземпляр нужного класса, а затем указатель на созданный объект транспортируется обратно в вызывающий класс. Между главным приложением и DLL этот указатель транспортируется как универсальный домен Object.

В действительности при вызове

... 
MyClassInstance=myClass::new(), 
...

выполняется цепочка подстановок и преобразований (представим, что DLL уже загружена в память):

  1. myClass является статическим классом с предикатом (а, не конструктором) new(...) на вызывающей стороне.
  2. Вызов предиката new() транспортируется в DLL и там вызывается настоящий конструктор MyClassInstance=myClass ::new(...).
  3. MyClassInstance трансформируется в объект с помощью преобразования MyClassObject=convert(object,MyClassInstance) .
  4. MyClassObject доставляется в вызывающий класс.
  5. MyClassObject в вызывающем классе приводится к домену MyClass с помощью преобразования MyClassInstance=convert(myClass,MyClassObject).

Таким образом, с практической точки зрения вызов

...
MyClassInstance = myClass::new(), 
...

состоялся, как вызов конструктора, и теперь результат его может быть использован, как обычно

MyClassInstance:callNeededPredicate().

И именно это обстоятельство обеспечивает практическую неизменность исходного кода вызывающего класса.

Механизмы Pzl-cистемы

Pzl-Система представляется классом pzl и включает библиотеки, именуемые PzlSystem.

Pzl-Система:

  • автоматически загружает DLL, содержащую класс, к которому производится обращение
  • транспортирует вызовы конструктора и возвращает указатель на экземпляр объекта
  • обеспечивает возможность взаимодействия классов, помещенных в различные DLL, а не только классов основного приложения и DLL
  • автоматически выгружает DLL, когда прекращено использование всех классов данной DLL
  • обеспечивает минимальные изменения в текстах программ
  • не требует существенного изменения структуры основного приложения

Pzl-Технология:

  • включает все неоходимые библиотеки и тексты классов, поддерживающих функционирование pzl-системы
  • позволяет легко встроить PZL-Механизмы в действующие проекты
  • автоматически генерирует все необходимые коды
  • содержит все необходимые инструменты для практического использования pzl-системы

PZL-Технология требует понимания концепций:

  • VIP-пакет (VipPack) как PZL-компонента (PzlComponent)
  • Представитель Компоненты (Component Delegate (proxy))
  • VIP проекты как Pzl-Контейнеры (PzlContainer)
  • Главное приложение как PZL-Порт (pzlPort)

Pzl-компонента (PzlComponent)

Pzl-компонента является обычным классом, помещенным в пакет системы Visual Prolog.

Главными особенностями являются:

  • этот класс наследует от класса pzlComponent
  • базовый интерфейс этого класса поддерживает интерфейс pzlComponent
  • базовый интерфейс этого класса содержит специальную константу-дескриптор componentDescriptor_C
  • имя интерфейса и имя класса должны отличаться
  • конструктор класса является единственным и представляется как new:(object AnyUserDefinedObject). Аргумент конструктора не имеет специльного назначения в рамках Pzl-системы.

Константа-дескриптор componentDescriptor_C принадлежит специальному домену pzlComponentInfo_D. Один из аргументов структурированного домена pzlComponentInfo_D определяет уникальный идентификатор компоненты. Идентификатор может иметь форму string или это может быть универсальный идентификатор, как это обычно принято в MSCOM технологии.

Компонента имеет дополнительно имя, которое может быть использовано для создания экземпляра компоненты. Это имя также является частью структуры константы-дескриптора componentDescriptor_C.

Код класса A после его преобразования в Pzl-компоненту приведен в ниже.

inrerface iA 
  supports pzlComponent 
constants 
  componentDescriptor_C:…componentInfo
    (
    componentAlias_C, 
    componentID_C, 
    ... 
    ). 
predicates 
  pA:(). 
end interface iA 
 
class a:iA 
constructors 
  new:(Object). 
end class a 
 
implement a 
  inherits pzlComponent 
clauses 
  new(_Object):- 
    ...
 
clauses 
  ... 
  Ob=b::new(This), 
  Ob:pB(). 
clauses 
  pA():- 
  ... 
end implement a

Класс B, преобразованный в Pzl-компоненту выглядит абсолютно также, кроме того, что используется другое имя класса.

Как видно, изменения претерпели только декларации класса и интерфейса, но не код имплементации.

Следует обратить внимание на то, что pzl-компонента ничего не знает ни о месте, где она сама расположена, ни о месте компоненты, с которой данная компонента взаимодействует.

Регистрация компонент

Для того, чтобы знать, где на данном компьютере размещается та или иная pzl-компонента, используется механизм регистрации компонент. Информация о регистрации компонент может быть сохранена либо в файле, который находится в распоряжении и под управлением пользователя, либо эта информация может быть сохранена в реестре Windows. Одна и та же компонента может быть зарегистрирована в нескольких местах.

Средства pzl-технологии могут зарегистрировать или снять с регистрации pzl-компоненты в любом из заданных мест раздельно или совместно в составе группы.

Три способа создания экземпляров

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

Существуют три способа вызова создания экземпляра pzl-компоненты:

  • путем вызова конструктора new(...)
  • путем вызова предиката newByName(...)
  • путем вызова предиката newByID(...)

Первый способ уже обсуждался выше и выглядит следующим образом:

... 
MyClassInstance =myClass::new(SomeObject), 
MyClassInstance:callNeededPredicate() 
...

Это соответствует обычным правилам VIP.

Второй способ использует pzl-систему явно и выглядит так:

...
MyClassObj=pzl::newByName(MyClass,...), 
MyClassInstance=tryConvert(iMyClass, MyClassObj), 
MyClassInstance:сallNeededPredicate(),

Здесь iMyClass является интерфейсом класса myClass, а “MyClass” является строковым именем класса (alias) myClass.

Третий способ также явно использует pzl-систему и выглядит так:

 
...
MyClassObj=pzl::newByID(str(MyClass),...), 
MyClassInstance=tryConvert(iMyClass, MyClassObj), 
MyClassInstance:сallNeededPredicate(),

Здесь iMyClass является интерфейсом класса myClass, а str(“MyClass”) является строковым представлением идентификатора класса.

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

uid(0xB5B1AE3D,0xBD01,0x4A29,0x9A,0x14,0x48,0x56,0x34,0x97,0xC7,0xC9).

Выбор способа использования зависит от обстоятельств, в частности от ответственности кода. Например, в ходе освоения техники программирования с использованием pzl-системы, можно пользоваться строковым идентификатором str(...), а в случае реального коммерческого программирования предпочтительнее использовать цифровой идентификатор uid(...)

Класс-делегат (proxy)

При использования обычного для VIP стиля программирования

...
MyClassInstance =myClass::new(SomeObject), 
MyClassInstance:callNeededPredicate() 
...

в случае, если вызывающий и вызываемый классы помещены в разные сущности (EXE и DLL или в разные DLL) , процесс создания экземпляра класса использует класс-делегат (proxy). При этом вызывающий класс вызывает предикат new() класса-делегата, а класс-делегат взаимодействует с PZL-системой, которая возвращает классу-делегату указатель на экземпляр вызываемого класса. Класс-делегат преобразует указатель, имеющий тип object в тип вызываемого класса и, таким образом, создание указателя экземпляра завершается.

Текст класса-делегата очень прост и единообразен, что создает возможность его автоматической генерации для каждой pzl-компоненты. Пример текста класса-делегата для класса "AboutDialog" приведен ниже

implement aboutDialog
   open core
 
constants
  className = "AboutDialogProxy".
  version = "1.0".
 
clauses
    classInfo(className, version).
 
clauses
  new(ObjIn)=convert(iAboutDialog,ObjOut):-
   ObjOut=pzl::new(iAboutDialog::componentID_C, ObjIn).
 
end implement aboutDialog

Пакет AboutDialog.pack имеет текст

#include@"AboutDialog\AboutDialog.ph"
#include@"AboutDialog\AboutDialogProxy.pro"

Средства pzl-технологии генерируют классы-делегаты автоматически.

Комбинированное использование оригинального класса и класса-делегата

В случаях, когд вызываемый и вызывающий классы помещены в одну сущность (в EXE или в одну и ту же DLL), взаимодействие классов в форме

...
MyClassInstance =myClass::new(SomeObject), 
MyClassInstance:CallNeededPredicate() 
...

происходит непосредственно и pzl-система не принимает участие во взаимодействии. Здесь действуют обычные правила VIP. Пакет в этом случае выглядит следующим образом

#include@"AboutDialog\AboutDialog.ph"
% privately used packages
#include @"pfc\string\string.ph"
% private interfaces
 #include @"resourceIdentifiers.i"
 
% implementations
#include @"AboutDialog\AboutDialog.pro"

С технологической точки зрения удобно иметь все упоминания о файлах, относящихся к pzl-компоненте - пакеты, представляющие оригинальный класс и класс-делегат, в одном пакете.

Возможность комбинированного использования соответствующих деклараций и имплементаций дает использование условной компиляции. В зависимости от значения параметра условной компиляции используется либо оригинальный класс, либо класс-делегат.

Пример текста такого комбинированного пакета приведен ниже.

#include @"AboutDialog\AboutDialog.ph"
 
#if iPzlConfig::useAboutDialogOriginal_C=true #then
% privately used packages
#include @"pfc\string\string.ph"
 
% private classes
 
% private interfaces
    #include @"resourceIdentifiers.i"
 
% implementations
    #include @"AboutDialog\AboutDialog.pro"
#else
    #include @"AboutDialog\AboutDialogProxy.pro"
#endif

Тот же принцип используется для комбинированного использования оригинального класса и класса-делегата в файле заголовка AboutDialog.ph.

Таким образом, если используется оригинальный класс, то константа useAboutDialogOriginal_C должна иметь значение true, а, если используется класс-делегат, то константа useAboutDialogOriginal_C должна иметь значение false.

Значение константы, которая определяет способ взаимодействия, определяется в файле PzlConfig.i, в котором собрана информация обо всех pzl-компонентах данной сущности (EXE или DLL).

Pzl-контейнер

Pzl-контейнер является проектом системы программирования Visual Prolog (один контейнер - один проект). Он может быть проектом, создающим исполняемое приложение (EXE) или проектом, создающим DLL.

Pzl-компонента "не знает", в каком контейнере она помещается. В отличие от pzl-компоненты pzl-контейнер "знает" какие pzl-компоненты он содержит. Все pzl-компоненты, которые помещены в данный pzl-контейнер, должны быть включены в предопределенный пакет PzlConfig.pack. Имплементация класса pzlConfig содержит обращения к классам pzl-компонент.

Ниже приведена структура PzlConfig, как это представляется в проектном окне IDE как главного приложения Exe, так и контейнера

PzlConfigStructure.png

Отсутствующие здесь файлы PzlConfig.cl и PzlConfig.ph не содержат информации по конкретным pzl-компонентам и включены в набор файлов, помещенных в директорию PzlSystem. Приведенные же три файла PzlConfig.i, PzlConfig.pro и PzlConfig.pack обеспечивают работу pzl-компонент, включенных в данный проект. Это обстоятельство позволяет перемещать pzl-компоненты из одного контейнера в другой без модификации остальных частей проектов-контейнеров. Средства pzl-технологии обеспечивают автоматически согласованную модификацию файлов пакета pzlConfig при добавлении или удалении соответсвующих pzl-компонент из проекта-контейнера.

Являясь обычным Vip проектом, pzl-контейнер может содержать не только pzl-компоненты, но и любые другие пакеты, соответствющие потребностям.

VpPuZzle. Обзор

Свойство pzl-технологии, заключающееся в том, что две pzl-компоненты, помещенные в один проект (генерирующий EXE или DLL), взаимодействуют непосредственно без участия pzl-системы, и взаимодействуют через pzl-систему, если они помещаются в разные проекты, позволяет легко делить приложения на части тогда, когда программист находит это удобным. Дополнительных изменений в проектах не требуется. Средства pzl-технологии позволяют производить такое перемещение без вникания в технические детали организации pzl-механизмов.

Ссылки