Несмотря на перманентные похороны Delphi, эта платформа построения Desktop приложений живет и здравствует, а со сменой владельца даже обретает второе дыхание и продолжает оставаться основным инструментом для тысяч разработчиков во всем мире.
Как и с любыми СУБД Delphi прекрасно взаимодействует с СУБД Caché.
Из Delphi можно подключиться к Caché, используя следующие интерфейсы:
- Caché Objects (объектный, используя библиотеки CacheObject.dll/CacheActiveX.dll);
- Caché SQL (реляционный, используя драйвер ODBC);
- Caché Direct (прямой, используя библиотеку Vism.ocx);
- Веб-сервисы;
- Сокеты.
В данной статье будут рассмотрены примеры использования объектного интерфейса при работе с СУБД 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 FilesCommon FilesInterSystemsCache:
- 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é, используя раннее связывание. Для работы с поздним связыванием этот раздел можно пропустить.
Итак, по порядку:
- выбираем пункт меню Component > Import Component...;
- выбираем для начала библиотеку типов CacheActiveX 2.0 Type Library;
- задаём имя нашей закладки, куда мы хотим установить наши компоненты, а также другие параметры;
- создаём наш модуль, пока без установки;
- повторяем пункты 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.
- создаём новый проект типа Package и добавляем в него все нами ранее созданные модули. Компилируем проект и инсталлируем наш пакет. Вот что в итоге у нас должно получиться:
Примечание: Для более ранних версий Delphi процесс создания модулей отличается незначительно:
- выбираем пункт меню Project > Import Type Library...;
- далее см. пункты выше.
- выбираем пункт меню Project > Import Type Library...;
Генерация пользовательских прокси-классов
В данном разделе описывается генерация пользовательских прокси-классов для работы с объектами в СУБД 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, и сохраняет результат в файл MIDLtest.odl
Внимание: Полученный таким образом файл предназначен для использования с библиотекой CacheActiveX.dll. Чтобы сгенерировать ODL-файл, предназначенный для работы со старой библиотекой, следует воспользоваться методом ExportODL класса %SYSTEM.OBJ
ПРИМЕР:
set list="%Library.Status,Sample.Person"
do $system.OBJ.ExportODL(list,"c:MIDLtest.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 %Boolean) As %Status [ Private,ServerOnly = 1 ]
{
; выводим имя класса и метода текущего контекста
write "Hello from Cache! (",$$$CurrentClass,":",$$$CurrentMethod,")",!quit $$$OK
}/// Запрос - хранимая процедура: демонстрация передачи простых типов данных.
Query test1(ABoolean As %Boolean, AInteger As %Integer, AString As %String, ADate As %Date, ATimeStamp As %TimeStamp) As %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 %List) As %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 %List) As %Status
{
; выводим на текущее устройство значение параметра AList
write AList; сохраняем значение в глобал
set ^pastest=AListquit $$$OK
}/// Демонстрация генерации ошибки.
ClassMethod test4() As %Status
{
quit $$$ERROR($$$GeneralError,"My error!")
}/// Демонстрация передачи заранее неизвестного количества параметров.
ClassMethod test5(Arg1... As %List) As %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 A As pas.a,
Output BLOB As %BinaryStream,
Output RS1 As %CharacterStream,
Output RS2 As %CharacterStream) As %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-файл, становятся доступны наиболее важные методы и свойства наших классов:
Внимание: Классы по работе с потоками и некоторые другие не следует импортировать из СУБД 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 и другие методы доступа.
Подробное описание методов класса TFactory, а также других классов, можно найти в 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;
Запрос Extent (выборка всех экземпляров хранимого класса)
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;
Запрос test1
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);
Метод test4. Обработка ошибок
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 FilesCommon FilesInterSystemsCache и зарегистрировать в системе некоторые из них с помощью утилиты regsvr32.exe.
ПРИМЕР:
regsvr32.exe /s "C:Program Files (x86)Common FilesIntersystemsCacheCacheQuery.ocx"
regsvr32.exe /s "C:Program Files (x86)Common FilesIntersystemsCacheCacheFormWizard.dll"
regsvr32.exe /s "C:Program Files (x86)Common FilesIntersystemsCacheCacheList.ocx"
regsvr32.exe /s "C:Program Files (x86)Common FilesIntersystemsCacheCacheActiveX.dll"
regsvr32.exe /s "C:Program Files (x86)Common FilesIntersystemsCachevism.ocx"
regsvr32.exe /s "C:Program Files (x86)Common FilesIntersystemsCacheTL.dll"
Исходники
- серверная часть
pas.xml
- клиентская часть
Unit1.dfm test_TLB.pas Unit1.pas
- сгенерированные прокси-классы
test.odl
Автор: servitRM