Работа с объектами СУБД Caché на примере Delphi


Несмотря на перманентные похороны Delphi, эта платформа построения Desktop приложений живёт и здравствует, а со сменой владельца даже обретает второе дыхание и продолжает оставаться основным инструментом для тысяч разработчиков во всём мире.
Как и с любыми другими СУБД, Delphi прекрасно взаимодействует с СУБД Caché.

Из Delphi можно подключиться к Caché, используя следующие интерфейсы:

В данной статье будут рассмотрены примеры использования объектного интерфейса при работе с СУБД Caché.

Но для начала приведу несколько коротких примеров на VBScript, которые вы можете запустить непосредственно из Windows Проводника.

Пример прямого доступа:

Set f = CreateObject("VISM.VisMCtrl.1")
f.Server="CN_IPTCP:localhost[1972]:_system:@ SYS"
f.NameSpace="SAMPLES"
f.Execute("=$zv") 'получение версии СУБД
WScript.Echo f.VALUE

Пример реляционного доступа:

Set cn=Createobject("ADODB.Connection") 
cn.ConnectionString="DRIVER={InterSystems ODBC35}; SERVER=127.0.0.1; PORT=1972; DATABASE=SAMPLES; UID=_system; PWD=SYS"
cn.open
WScript.Echo "Succesfully!"

Пример объектного доступа:

Set f = CreateObject("CacheActiveX.Factory")
Set rs = CreateObject("CacheActiveX.ResultSet")
If Not f.IsConnected() Then

  f.Connect("cn_iptcp:127.0.0.1[1972]:SAMPLES:_SYSTEM:SYS")

  Set rs=f.DynamicSQL("select TOP 3 * from Sample.Person")
  rs.Execute()
  while rs.Next
    WScript.Echo rs.Get("SSN") 'выводим поле SSN первых трёх записей из таблицы Sample.Person
  wend

  rs.Close()
  Set person = f.Static("Sample.Person")
  age=person.CurrentAge(45678) 'вызываем метод класса Sample.Person

  WScript.Echo age
End If

Похожим образом вы можете работать с СУБД Caché, используя JScript, Visual Basic, C++ Builder, и т.д.

Примечание: Для Java и .NET предусмотрено несколько нативных интерфейсов, которые обеспечивают гораздо больше возможностей.

Позднее и раннее связывание

Существует два подхода при работе с объектами Caché из Delphi:

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

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

Раннее связывание лишено этих недостатков, но за это приходится платить тем, что при изменении интерфейса пользовательских классов в БД или при переходе на новую версию СУБД Caché необходимо перегенерировать прокси-классы для клиентского приложения.

Комбинируя оба этих подхода можно добиться оптимальной производительности и удобства в работе.

В данной статье будет в основном использоваться раннее связывание, так как данный подход меньше описан по сравнению с поздним связыванием.

Для работы Caché Objects используются две библиотеки, которые по умолчанию устанавливаются в C:\Program Files\Common Files\InterSystems\Cache\:

  • CacheObject.dll (устаревшая);
  • CacheActiveX.dll (рекомендуемая).

Начиная с версии Caché 5.1, разработчики InterSystems настоятельно рекомендуют использовать новую библиотеку CacheActiveX.dll. Библиотека CacheObject.dll оставлена лишь для совместимости со старыми приложениями. Поэтому в данной статье я буду основываться на CacheActiveX.dll.

Об отличиях данных библиотек и нюансах, которые нужно учитывать при переходе на новую версию, можно почитать в Upgrading from CacheObject.dll.

В каталоге выше можно найти и другие файлы, которые могут Вам пригодиться:

  • DelphiCallback.dll;
  • CacheList.ocx;
  • CacheQuery.ocx;
  • VISM.ocx.

Импорт и установка компонент Caché Objects ActiveX в Delphi

В данном разделе описывается установка основных классов и интерфейсов в среде Delphi для работы с объектами в СУБД Caché, используя раннее связывание. Для работы с поздним связыванием этот раздел можно пропустить.

Итак, по порядку:

  1. выбираем пункт меню Component > Import Component…;

    image

    image

  2. выбираем для начала библиотеку типов CacheActiveX 2.0 Type Library;

    image

  3. задаём имя нашей закладки, куда мы хотим установить наши компоненты, а также другие параметры;

    image

  4. создаём наш модуль, пока без установки;

    image

  5. повторяем пункты 2-4 для следующих библиотек:
    • CacheActiveX 2.0 Type Library;
    • CacheList ActiveX Control module;
    • CacheQuery ActiveX Control module;
    • DelphiCallback 1.0 Type Library;
    • VisM 7.2 ActiveX Control;
    • TL 1.0 Type Library.

    Примечание: Создать модули можно и с помощью утилиты tlibimp.exe, входящей в поставку Delphi.

  6. создаём новый проект типа Package и добавляем в него все нами ранее созданные модули. Компилируем проект и инсталлируем наш пакет. Вот что в итоге у нас должно получиться:

    image

    image

    Примечание: Для более ранних версий Delphi процесс создания модулей отличается незначительно:

    • выбираем пункт меню Project > Import Type Library…;

      image

    • далее см. пункты выше.

Генерация пользовательских прокси-классов

В данном разделе описывается генерация пользовательских прокси-классов для работы с объектами в СУБД Caché, используя раннее связывание. Для работы с поздним связыванием этот раздел можно пропустить.

Для начала необходимо сгенерировать ODL-файл, содержащий все необходимые нам пользовательские классы. Для этого следует воспользоваться утилитой odl_generator.exe, поставляемой с СУБД Caché.

ПРИМЕР:
odl_generator.exe -conn cn_iptcp:localhost[1972]:USER:_system:SYS -class-list test.txt -lib-name test -dir MIDL
В данном примере утилита подключается к области USER, генерирует прокси-классы для классов перечисленных в файле test.txt, и сохраняет результат в файл MIDL\test.odl

Внимание: Полученный таким образом файл предназначен для использования с библиотекой CacheActiveX.dll. Чтобы сгенерировать ODL-файл, предназначенный для работы со старой библиотекой, следует воспользоваться методом ExportODL класса %SYSTEM.OBJ

ПРИМЕР:
  set list="%Library.Status,Sample.Person"
  
do $system.OBJ.ExportODL(list,"c:\MIDL\test.odl","-d",.err)

Итак, ODL-файл получен, теперь следует его скомпилировать, чтобы получить TLB-файл, а затем и PAS-файл. Для этого можно воспользоваться утилитами midl.exe или mktypelib.exe, входящими в комплект разработчика Visual C++.

ПРИМЕР:
midl /I . test.odl /tlb test.tlb
tlibimp.exe -C- -P+ -Hr- -Ha- -Hs- -XM- test.tlb

Примечание: Все вышеперечисленные этапы вы можете автоматизировать с помощью MAC-программы СУБД Caché.

Подготовительные работы на сервере

Создадим в нашей тестовой базе следующие классы данных:

/// Встраиваемый класс
Class pas.s Extends %SerialObject
{

/// Целое число (64-бит).
Property
aInteger As %Integer;

/// Строка. Максимальная длина по умолчанию - 50 символов.
Property
aString As %String;

}

/// Вспомогательный хранимый класс.
Class pas.a Extends %Persistent
{

/// Индекс на поле aA;
Index
aAIndex On aA;

/// Отношение один-ко-многим, мощность "один". В SQL преобразуется в foreign key.
Relationship
aA As pas.test [ Cardinality = one, Inverse = aChilds ];

Property aInteger As %Integer;

Property aString As %String;

}

/// Подключение вспомогательного файла %occIO.inc с макросами.
Include 
%occIO

/// Основной хранимый класс.
Class pas.test Extends %Persistent
{

/// Задаём поля, возвращаемые при выборке всех экземпляров данного класса;
Parameter 
EXTENTQUERYSPEC As ROWSPEC [ Flags = LIST ] =  aBoolean,aInteger,aString,aDate,aTimeStamp";

/// Булево значение (true/false/null);
Property 
aBoolean As %Boolean;

Property aInteger As %Integer;

Property aString As %String;

/// Дата;
Property 
aDate As %Date;

/// Дата+время;
Property 
aTimeStamp As %TimeStamp;

/// Символьный поток (CLOB);
Property 
aMemo As %GlobalCharacterStream;

/// Двоичный поток (BLOB);
Property 
aPhoto As %GlobalBinaryStream;

/// Отношение один-ко-многим, мощность "много". В SQL отсутствует аналог.
Relationship 
aChilds As pas.a Cardinality = many, Inverse = aA ];

/// Свойство-объект встраиваемого класса;
///<br>В SQL каждое свойство встраиваемого класса становится отдельным 
///<br>свойством класса-контейнера.
Property 
aS As pas.s;

/// Коллекция-список строк;
///<br>В SQL это поле содержит значения, разделённые заданным разделителем, например запятой. 
Property 
aListOfString As list Of %String;

/// Коллекция-список объектов вспомогательного класса;
///<br>В SQL это поле содержит значения первичных ключей объектов, разделённые заданным разделителем, например запятой. 
Property 
aListOfA As list Of pas.a;

/// Коллекция-массив строк;
///<br>В SQL формируется виртуальная таблица. 
Property 
aArrOfString As array Of %String;

/// Коллекция-массив объектов вспомогательного класса;
///<br>В SQL формируется виртуальная таблица. 
Property 
aArrOfA As array Of pas.a;

/// Метод экземпляра класса.
/// <br>Переопределяем встроенное событие, возникающее перед сохранением объекта.
Method 
%OnBeforeSave(insert As %BooleanAs %Status Private,ServerOnly = 1 ]
{
  
; выводим имя класса и метода текущего контекста
  
write "Hello from Cache! (",$$$CurrentClass,":",$$$CurrentMethod,")",!

  quit $$$OK
}

/// Запрос - хранимая процедура: демонстрация передачи простых типов данных.
Query 
test1(ABoolean As %BooleanAInteger As %IntegerAString As %String, ADate As %DateATimeStamp As %TimeStampAs %SQLQuery(CONTAINID 1
ROWSPEC "ID:%String,aBoolean:%Boolean,aInteger:%Integer,aString:%String,aDate:%Date,aTimeStamp:%TimeStamp"
) [ SqlProc ]
{
SELECT %ID,aBoolean,aInteger,aString,aDate,aTimeStamp FROM pas.test WHERE
(aBoolean=:ABoolean or :ABoolean is null)
AND (
aInteger=:AInteger or :AInteger is null)
AND (
aString=:AString or :AString is null)
AND (
aDate<:ADate or :ADate is null)
AND (
aTimeStamp<=:ATimeStamp or :ATimeStamp is null)
}

/// Запрос - хранимая процедура: демонстрация передачи параметра встроенного типа данных "список"
///<br>и использование его в запросе совместно с конструкцией %INLIST.
Query 
test2(AList As %ListAs %SQLQuery(CONTAINID 1
ROWSPEC "ID:%String,aBoolean:%Boolean,aInteger:%Integer,aString:%String,aDate:%Date,aTimeStamp:%TimeStamp"
) [ SqlProc ]
{
SELECT %ID,aBoolean,aInteger,aString,aDate,aTimeStamp FROM pas.test WHERE ID %INLIST :AList
}

/// Метод класса.
ClassMethod 
test3(AList As %ListAs %Status
{
  
; выводим на текущее устройство значение параметра AList
  
write AList

  ; сохраняем значение в глобал
  
set ^pastest=AList

  quit $$$OK
}

/// Демонстрация генерации ошибки.
ClassMethod 
test4() As %Status
{
  
quit $$$ERROR($$$GeneralError,"My error!")
}

/// Демонстрация передачи заранее неизвестного количества параметров.
ClassMethod 
test5(Arg1... As %ListAs %Status
{
  
; выводим общее количество переданных параметров и их значения
  
write "Invocation has ",$get(Arg1, 0)," element",$select(($get(Arg1, 0)=1):"", 1:"s"),!
  
for i = 1 : 1 : $get(Arg1, 0)
  
{
     
write:($data(Arg1(i))>0) "Argument[",i,"]:",?15,$get(Arg1(i),"<NULL>"),!
  
}
  
quit $$$OK
}

/// Демонстрация передачи параметров более сложных типов: объекта нашего класса и потоков.
/// <br>Также демонстрируется передача параметра по ссылке и выходных параметров.
/// <br>Параметры:
/// <br><var>ID</var> - строка;
/// <br><var>A</var> - объект вспомогательного класса;
/// <br><var>BLOB</var> - двоичный поток;
/// <br><var>RS1</var> - символьный поток, содержащий данные запроса в формате Borland ® MyBase (DataSnap (TM)) XML DataSet;
/// <br><var>RS2</var> - символьный поток, содержащий данные запроса в формате Borland ® MyBase (DataSnap (TM)) XML DataSet;
ClassMethod 
test6(
  
ID As %String,
  
ByRef As pas.a,
  
Output BLOB As %BinaryStream,
  
Output RS1 As %CharacterStream,
  
Output RS2 As %CharacterStreamAs %Status
{
  
// меняем одно из свойств объекта, переданного по ссылке
  
set A.aString=999
  
  
// создаём объект двоичного потока
  
set BLOB=##class(%GlobalBinaryStream).%New()

  // записываем в поток данные
  
do BLOB.Write("123")
  
  
// создаём объекты символьного потока
  
set RS1=##class(%GlobalCharacterStream).%New()
  
set RS2=##class(%GlobalCharacterStream).%New()
  
  
// создаём объект пользовательского класса, позволяющего выгружать данные в формат XML для TClientDataSet
  
set cds=##class(%XML.ZMyBaseDataSet).%New()

  // подготавливаем запрос
  
do cds.Prepare("select * from pas.a where id %inlist ?")

  // передаём данные в запрос
  
do cds.SetArgs($listbuild(1,2,3,9))

  // выгружаем данные в символьный поток в формате XML
   
do cds.XMLExportToStream(.RS1)

  // закрываем (инициализируем заново) объект для выполнения другого запроса
  
do cds.Close()
  
do cds.Prepare("select ID,aBoolean,aInteger,aString,aDate,aTimeStamp from pas.test")
  
do cds.XMLExportToStream(.RS2)
  
do cds.Close()

  // возвращаем статус "Успешно"
  
quit $$$OK
}

}

Сгенерируем для них прокси-классы, а также для следующих классов:

  • %Library.ArrayOfDataTypes
  • %Library.ArrayOfObjects
  • %Library.ListOfObjects
  • %Library.ListOfDataTypes
  • %Library.RelationshipObject

Теперь, если подключить к проекту сгенерированный PAS-файл, становятся доступны наиболее важные методы и свойства наших классов:

image
увеличить

Внимание: Классы по работе с потоками и некоторые другие не следует импортировать из СУБД Caché, так как они уже зашиты в библиотеку CacheActiveX.dll и несовместимы со сгенерированными прокси-классами.

Подключение к СУБД Caché

Позднее связывание (старая библиотека):

var _f:variant;
begin
  _f:=CreateOleObject('CacheObject.Factory');
  if _f.Connect(_f.ConnectDlg('+%up')) then ShowMessage('OK') else ShowMessage('ERROR');

Позднее связывание (новая библиотека):

var _f:variant;
begin
  _f:=CreateOleObject('CacheActiveX.Factory');
  if _f.Connect(_f.ConnectDlg('+%up')) then ShowMessage('OK') else ShowMessage('ERROR');

Раннее связывание (новая библиотека):

type
  Tfm = class(TForm)
    f: TFactory;
  ...
begin
  ...
  if f.Connect1(f.ConnectDlg('+%up')) then ShowMessage('OK') else ShowMessage('ERROR');
  ...

Внимание: Старая библиотека использует сервис %Service_CacheDirect и только неаутентифицированный доступ, новая – %Service_Bindings и другие методы доступа.

Подробное описание методов класса TFactobry, а также других классов, можно найти в ActiveX API Reference

Примеры вызова запросов и методов, используя раннее связывание

Использование функциональности CallBack

Для использования функциональности CallBack из Delphi следует воспользоваться готовым классом TCallback из файла DelphiCallback.dll.

var f:TFactory;
    Callback1: TCallback;
    mm: TMemo;
  ...
  f.SetOutput(Callback1.OleObject);
  ...
procedure Tfm.Callback1TextChanged(Sender: TObject; const p_bstrText: WideString);
begin
  mm.Lines.Append(p_bstrText);
end;
Удаление всех данных
  mm.Lines.Text:='KillExtent'#10#13;
  // удаляем данные
  test_(f.Static('pas.test')).SYS_KillExtent(1);
  a(f.Static('pas.a')).SYS_KillExtent(1);
Пример создания, заполнения, сохранения и освобождения объектов различных типов

Про особенности закрытия объектов при работе через ActiveX можно почитать в статье Сергея Кудинова: Особенности закрытия объектов при работе через ActiveX, CPP-binding.

Пример кода:

...
uses test_TLB, AxCtrls, ComObj, ActiveX, Types;
...
const
  N = 3;
var
  i: integer;
  _t: test_;
  _a: A;
  _s: s;
  rel: RelationshipObject;
  listStr: ListOfDataTypes;
  listA: ListOfObjects;
  arrStr: ArrayOfDataTypes;
  arrA: ArrayOfObjects;
  stream: IDispatch;
begin

  Screen.Cursor := crSQLWait;
  mm.Lines.Text:='Save'#10#13;
  try
    try

      // создаём новый объект класса pas.test
      _t := test_(f.New('pas.test'));

      // далее заполняем его данными
      _t.aBoolean := true;
      _t.aInteger := 50;
      _t.aString := 'Тестовая строка';

      // так мы можем присвоить null скалярному типу данных 
      // (%Integer,%Boolean,%Date и т.д.)
      Variant(_t).aDate := nil;

      _t.aDate := _t.aDateDisplayToLogical('02.03.2001');
      // или t.aDate:=StrToDate('02.03.2001');
      _t.aTimeStamp := _t.aTimeStampDisplayToLogical('1900-01-02 12:34:55');
      // или t.aTimeStamp:=StrToDateTime('02.01.1900 12:34:55');

      // заполняем данными символьный поток
      stream := _t.aMemo;
      ICharStream(stream).Write('Символьный поток');
      stream := nil;

      // заполняем данными бинарный поток
      stream := _t.aPhoto;
      IBinaryStream(stream).FileRead('C:\test.jpg');
      stream := nil;

      // создаём N "дочерних" объектов класса pas.a
      rel := RelationshipObject(_t.aChilds);
      for i := 1 to N do
      begin
        _a := A(f.New('pas.a'));
        _a.aInteger := i;
        _a.aString := 'rel' + IntToStr(i);
        rel.Insert(_a);
        _a.SYS_Close;
      end;
      _t.aChilds := rel;
      rel.SYS_Close;

      // заполняем данными встраиваемый объект
      _s := s(_t.aS_);
      _s.aInteger := 1;
      _s.aString := 's1';
      _s.SYS_Close;

      // заполняем данными список простых типов. В данном случае строк
      listStr := ListOfDataTypes(f.New('%ListOfDataTypes'));
      for i := 1 to N do
        listStr.Insert('str' + IntToStr(i));
      _t.aListOfString := listStr;
      listStr.SYS_Close;

      // создаём объекты класса pas.a и заполняем ими список
      listA := ListOfObjects(f.New('%ListOfObjects'));
      for i := 1 to N do
      begin
        _a := A(f.New('pas.a'));
        _a.aInteger := i;
        _a.aString := 'listA' + IntToStr(i);
        listA.Insert(_a);
        _a.SYS_Close;
      end;
      _t.aListOfA := listA;
      listA.SYS_Close;

      // заполняем данными массив простых типов. В данном случае строк
      arrStr := ArrayOfDataTypes(f.New('%ArrayOfDataTypes'));
      for i := 1 to N do
        arrStr.SetAt('astr' + IntToStr(i), 'arraykey' + IntToStr(i));
      _t.aArrOfString := arrStr;
      arrStr.SYS_Close;

      // создаём объекты класса pas.a и заполняем ими массив
      arrA := ArrayOfObjects(f.New('%ArrayOfObjects'));
      for i := 1 to N do
      begin
        _a := A(f.New('pas.a'));
        _a.aInteger := i;
        _a.aString := 'arrayA' + IntToStr(i);
        arrA.SetAt(_a, 'arraykey' + IntToStr(i));
        _a.SYS_Close;
      end;
      _t.aArrOfA := arrA;
      arrA.SYS_Close;

      // сохраняем наш объект в базу в рамках одной транзакции. До этого 
      // все изменения объекта производились в оперативной памяти на стороне 
      // клиента.
      _t.SYS_Save(0);

      // читаем данные бинарного потока для отображения на экране фотографии
      stream := _t.aPhoto;
      SetOlePicture(img.Picture, IBinaryStream(stream).GetPicture);
      stream := nil;

      // закрываем объект на сервере. Список незакрытых объектов вы можете 
      // просмотреть в Портале, меню "Процессы" (%sysOrefs).
      // Незакрытые объекты в дальнейшем чреваты неприятностями в виде 
      // утечек памяти на сервере и заблокированности данных.
      _t.SYS_Close;
      // закрываем объект на клиенте
      _t := nil;

      mm.Lines.Append('OK');
    except
      on E: Exception do
      begin
        mm.Lines.Append(E.Message);
      end;
    end;
  finally
    Screen.Cursor := crDefault;
    // принудительно синхронизируем состояние объектов на сервере, 
    // согласно их состоянию на клиенте
    f.ForceSync;
  end;</source>
<h5>Запрос Extent (выборка всех экземпляров хранимого класса)</h5>
<source lang="delphi">var mm: TMemo;
    rs: TResultSet;
  ...
  mm.Lines.Text:='Extent'#10#13;
  rs.ConnectTo(IResultSet(f.ResultSet('pas.test', 'Extent')));
  rs.Execute;
  while rs.Next do
  begin
    mm.Lines.Append(Format('ID = %s',[rs.GetDataAsString(1)]));
    mm.Lines.Append(Format('aBoolean = %s',[rs.Get('aBoolean')]));
    mm.Lines.Append(Format('aInteger = %s',[rs.Get('aInteger')]));
    mm.Lines.Append(Format('aString = %s',[rs.Get('aString')]));
    mm.Lines.Append(Format('aDate = %s',[rs.Get('aDate')]));
    mm.Lines.Append(Format('aTimeStamp = %s',[rs.Get('aTimeStamp')]));
    mm.Lines.Append('-----');
  end;
  rs.Close;
  rs.Disconnect;</source>
<h5>Запрос test1</h5>
<source lang="delphi">var
  i: integer;
...
  mm.Lines.Text:='test1'#10#13;
  rs.ConnectTo(IResultSet(f.ResultSet('pas.test', 'test1')));
  rs.SetParam(1, null);
  rs.SetParam(2, 50);
  rs.SetParam(3, null);
  rs.SetParam(4, '03.03.2001');
  rs.SetParam(5, '1900-01-02 12:34:55.0');
  rs.Execute;
  while rs.Next do
  begin
    for i := 1 to rs.GetColumnCount do
      mm.Lines.Append(rs.GetColumnName(i)+' = '+rs.GetDataAsString(i));
    mm.Lines.Append('-----');
  end;
  rs.Close;
  rs.Disconnect;
Запрос test2
var
  i: integer;
  syslist: TSyslist;
...
  mm.Lines.Text:='test2'#10#13;

  // заполняем объект класса %List
  syslist.Clear;
  syslist.Add(1);
  syslist.Add(2);
  syslist.Add(3);

  rs.ConnectTo(IResultSet(f.ResultSet('pas.test', 'test2')));
  rs.Execute(syslist.DefaultInterface);
  while rs.Next do
  begin
    for i := 1 to rs.GetColumnCount do
      mm.Lines.Append(rs.GetColumnName(i)+' = '+rs.GetDataAsString(i));
    mm.Lines.Append('-----');
  end;
  rs.Close;
  rs.Disconnect;
Метод test3. Работа с объектом класса TSysList (тип %List в Caché)
  mm.Lines.Text:='test3'#10#13;

  // заполняем объект класса %List
  syslist.Clear;
  syslist.Add('16');
  syslist.Add('42');
  syslist.Add('35');

  test_(f.Static('pas.test')).test3(syslist.DefaultInterface);</source>
<h5>Метод test4. Обработка ошибок</h5>
<source lang="delphi">  mm.Lines.Text:='test4'#10#13;
  try
    test_(f.Static('pas.test')).test4();
  except
    on E: Exception do
    begin
      mm.Lines.Append(E.Message);
    end;
  end;
Метод test5
  mm.Lines.Text:='test5'#10#13;

  // заполняем объект класса %List
  syslist.Clear;
  syslist.Add('16');
  syslist.Add('42');
  syslist.Add('35');

  test_(f.Static('pas.test')).test5(syslist.DefaultInterface);
Метод test6

В данном примере используется класс %XML.ZMyBaseDataSet, который можно найти здесь. С его помощью можно на сервере формировать данные в формате Borland ® MyBase (DataSnap (TM)) XML DataSet. В том числе и для веб-сервисов.

cds1,cds2:TClientDataSet;
...
var _a:a;
    __a,blob,rs1,rs2:IDispatch;
    cs1,cs2:ICharStream;
begin
  mm.Lines.Text:='test6'#10#13;
  try
    _a:=a(f.OpenId('pas.a','1'));
    __a:=_a;
    test_(f.Static('pas.test')).test6('1',__a,blob,rs1,rs2);

    cs1:=ICharStream(rs1);
    cs2:=ICharStream(rs2);

    mm.Lines.Append('A.aString = '+_a.aString);

    mm.Lines.Append('BLOB.Size = '+IntToStr(IBinaryStream(blob).size));
    mm.Lines.Append('RS1.Size = '+IntToStr(cs1.size));
    mm.Lines.Append('RS2.Size = '+IntToStr(cs2.size));

    cds1.XMLData:=cs1.Data;
    cds2.XMLData:=cs2.Data;
  finally
    _a.SYS_Close;
    _a:=nil;
    __a:=nil;
    blob:=nil;
    rs1:=nil;
    rs2:=nil;
    cs1:=nil;
    cs2:=nil;
    f.ForceSync;
  end;

Для поддержки классом %XML.ZMyBaseDataSet данных в Unicode, а также других нереализованных типов данных Вам необходимо будет его доработать самостоятельно.

Распространение приложения

Для установки с приложением всех необходимых драйверов на новый компьютер следует скопировать все (для простоты. Набор нужных файлов можно ограничить) файлы из каталога C:\Program Files\Common Files\InterSystems\Cache\ и зарегистрировать в системе некоторые из них с помощью утилиты regsvr32.exe.

ПРИМЕР:
regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheQuery.ocx"
regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheFormWizard.dll"
regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheList.ocx"
regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\CacheActiveX.dll"
regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\vism.ocx"
regsvr32.exe /s "C:\Program Files (x86)\Common Files\Intersystems\Cache\TL.dll"

Исходники
  • серверная часть
    pas.xml
  • клиентская часть
    Unit1.dfm
    test_TLB.pas
    Unit1.pas
  • сгенерированные прокси-классы
    test.odl

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *