Приклади застосування потокової системи VCL, Комерція, Різне, статті

Потокове система VCL – незамінний помічник Delphi-програміста. У цьому можна переконатися, ознайомившись із рішеннями, запропонованими вашій увазі в цій статті. Тут представлені приклади використання потокової системи VCL і конкретні рішення прикладних задач, використання яких на практиці довело свою спроможність.

Потокове система призначена для вирішення найрізноманітніших прикладних задач. До їх числа можна віднести:



Основна ідея


В основу розглянутих тут рішень покладено ідею представлення даних об'єкта у вигляді потоку байтів. З таким потоком можна зробити практично все що завгодно: записати на диск, зберегти в memo-поле, упакувати в рядок або OleVariant, помістити в буфер обміну і виконати багато інших маніпуляції. Щоб представити дані об'єкта у вигляді потоку байтів, зовсім не обов'язково писати власні процедури – Вони вже реалізовані в потокової системі.

Основна вимога – об'єкт повинен бути компонентом, нехай невізуальні, нехай не зареєстрованим у палітрі компонентів, достатньо того, щоб він був спадкоємцем класу TComponent.

Якщо у вас вже є такий об'єкт, то збереження його даних в потоці і відновлення з потоку не представляє особливої ​​складності. Зберегти дані об'єкта в потоці можна за допомогою методів TStream.WriteComponent або TStream.WriteComponentRes, а відновити – за допомогою методів TStream.ReadComponent і TStream.ReadComponentRes відповідно. Зазначені методи працюють з різними форматами потоку даних компонента. Якщо використовуються методи WriteComponent і ReadComponent, то в потоці зберігаються лише дані компонента. Якщо WriteComponentRes і ReadComponentRes, то в потоці присутній заголовок Windows-ресурсу, за яким слідують дані самого компонента, такий потік є Windows-ресурсом.


Збереження результатів редагування та захист від збоїв


Збереження даних об'єкта в потоці використовується насамперед для збереження об'єкта у файлі. У VCL в модулі Classes реалізовані процедури WriteComponentResFile і ReadComponentResFile. Перша зберігає дані об'єкта в бінарному файлі, а друга відновлює дані об'єкту з бінарного файлу. Якщо ж ви хочете зберігати дані в текстовому форматі, то потокове система допоможе конвертувати бінарного формату в текстовий (ObjectBinaryToText) і назад (ObjectTextToBinary).
Продемонструю збереження в потік і перетворення в текстовий формат на прикладі:



/ / Процедура запису компонента Instance в текстовий потік Stream
procedure SaveComponentAsTextStream (Stream: TStream; Instance: TComponent);
var
inS: TMemoryStream;
begin
inS := TMemoryStream.Create;
try
/ / Записати компонент в потік в пам'яті
inS.WriteComponent(Instance);
/ / Повернути зміщення
inS.Position := 0;
/ / Перетворити бінарні дані в текстові
ObjectBinaryToText(inS, Stream);
finally
inS.Free;
end;
end;


/ / Процедура читання компонента Instance з текстового потоку Stream
procedure LoadComponentAsTextStream (Stream: TStream; Instance: TComponent);
var
outS: TMemoryStream;
begin
outS := TMemoryStream.Create;
try
/ / Перетворити текст у бінарні дані
ObjectTextToBinary(Stream, outS);
/ / Повернути зміщення
outS.Position := 0;
/ / Читати компонент з потоку
outS.ReadComponent(Instance);
finally
outS.Free;
end;
end;

Головний недолік збереження даних компонента в текстовому форматі – тривалий час виконання процедур перетворення одного формату в іншій, особливо при виклику процедури ObjectTextToBinary. Якщо змиритися з цим недоліком, то ви отримуєте надзвичайно просту реалізацію, універсальність і, звичайно ж, текстовий формат даних. Файли в такому форматі можна читати і правити в будь-якому текстовому редакторі. Дуже зручно в такому файлі зберігати параметри конфігурації програми (без особливих зусиль ви отримуєте свого роду ini-файл); дані програми-редактора (вам не потрібно буде придумувати власні формати зберігання даних і писати нетривіальні процедури перетворення програмного представлення даних в текстове і навпаки), і, наприклад, критично важливі програмні дані, які програма систематично зберігає на диску (такі дані легше читати і аналізувати).


Взаємодія з буфером обміну


Користувачі (в тому числі і програмісти) звикли до того, що в будь-якому редакторі є функції взаємодії з буфером обміну. Реалізація цих функцій – не таке вже складна справа. Дані компонента можна помістити в буфер обміну за допомогою методу TClipboard.SetComponent, і витягти з буфера за допомогою методу TClipboard.GetComponent. Ці методи поміщають і витягають дані компонента в форматі CF_COMPONENT, який реєструється бібліотекою VCL (модуль Clipbrd) у момент завантаження програми.

Формат CF_COMPONENT буде зрозумілий тільки вашій програмі, і це цілком влаштує кінцевих користувачів, але, цілком можливо, не влаштує вас – програміста, тому що функції взаємодії з буфером обміну з незрозумілих причин не працює, і ви вже не перший день намагаєтеся зрозуміти чому. Розібратися в цьому допоможуть процедури CopyComponentAsText і PasteComponentAsText, які для взаємодії з буфером обміну використовують текстовий формат CF_TEXT:



/ / Процедура копіювання даних компонента Instance в буфер обміну в текстовому форматі
procedure CopyComponentAsText(Instance: TComponent);
const
END_OF_STR: Char = #0;
var
inS: TMemoryStream;
outS: TMemoryStream;
begin
outS := nil;
inS := TMemoryStream.Create;
try
/ / Записати компонент в потік
inS.WriteComponent(Instance);
inS.Position := 0;
outS := TMemoryStream.Create;
/ / Перетворити записані дані в текстовий формат
ObjectBinaryToText(inS, outS);
/ / Записати термінальний символ
outS.WriteBuffer(END_OF_STR, 1);
/ / Записати дані в буфер обміну
Clipboard.SetTextBuf(outS.Memory);
finally
inS.Free;
outS.Free;
end
end;


/ / Процедура відновлення даних компонента Instance з буфера обміну
function PasteComponentAsText(Instance: TComponent): Boolean;


procedure GetClipboardTextToStream(Stream: TStream);
var
S: String;
begin
S := Clipboard.AsText;
Stream.WriteBuffer(PChar(S)^, Length(S));
end;


var
inS: TMemoryStream;
outS: TMemoryStream;
begin
Result := False;
if not Clipboard.HasFormat(CF_TEXT) then Exit;


outS := nil;
inS := TMemoryStream.Create;
try
try
GetClipboardTextToStream(inS);
inS.Position := 0;
outS := TMemoryStream.Create;
ObjectTextToBinary(inS, outS);
outS.Position := 0;
outS.ReadComponent(Instance);
Result := True;
except
{};
end;
finally
inS.Free;
outS.Free;
end;
end;

Майте на увазі, що перетворення бінарного формату в текстовий і назад займе деякий час. Втім, кінцеві користувачі помітять це, тільки якщо до процедури звернутися кілька разів поспіль, наприклад в циклі.


Клонування об'єктів


Інший приклад застосування потокової системи – створення точної копії об'єкта (клонування об'єкта). Функція клонування об'єкта являє собою реалізацію класичного шаблону проектування (design pattern) Prototype (див. [1]), суть якого полягає в конструюванні об'єкта за його прототипу.



/ / Створює клон компонента Instance, встановлює у клона властивості Owner, Parent
function CloneComponent (AOwner, AParent, Instance: TComponent): TComponent;
var
SaveName: TComponentName;
begin
if Instance = nil then
begin
Result := nil;
Exit;
end;
SaveName := Instance.Name;
try
Instance.Name: = ""; / / щоб ім'я компонента не потрапило в потік
Result := TComponentClass(Instance.ClassType).Create(AOwner);
try
CopyComponent(Instance, Result, AParent);
except
Result.Free;
Result := nil;
raise;
end;
finally
Instance.Name := SaveName;
end;
end;


/ / Копіює дані з компонента SrcComp в DstComp, якщо необхідно, встановлює батька у компонента DstComp
procedure CopyComponent(SrcComp, DstComp, AParent: TComponent);
var
S: TMemoryStream;
begin
if not (DstComp is SrcComp.ClassType) then
raise Exception.CreateFmt ("Дані екземпляра класу% s не можуть бути" +
"Присвоєно екземпляру класу% s",
[SrcComp.ClassName, DstComp.ClassName]);
S := TMemoryStream.Create;
try
S.WriteComponent(SrcComp);
S.Position := 0;
ReadComponentParented(nil, AParent, DstComp, S);
finally
S.Free;
end;
end;


/ / Читає компонент Instance з потоку S
procedure ReadComponentParented(AOwner, AParent: TComponent;
var Instance: TComponent; S: TStream);
var
R: TReader;
begin
R := TReader.Create(S, 256);
try
R.Parent := AParent;
if Instance = nil then
Instance := R.ReadRootComponent(AOwner)
else
R.ReadRootComponent(Instance);
finally
R.Free;
end;
end;

Функція CloneComponent викликає процедуру CopyComponent, в якій дані SrcComp записуються в потік, а потім читаються з потоку в DstComp. Хотів би звернути вашу увагу на те, що при записі компонента SrcComp в потік потрапляють лише значення, відмінні від значень за замовчуванням. Тому не дивуйтеся, якщо при прямому використанні процедури CopyComponent (тобто при використанні цієї процедури в обхід CloneComponent) ви виявите відмінності в значеннях властивостей в SrcComp і DstComp. Це може статися, бо компонент SrcComp «промовчить» про ті властивості, значення яких збігаються зі значеннями, що встановлюються в його конструкторі. Для вирішення цієї проблеми необхідно перед викликом процедури CopyComponent встановити у властивостей компонента DstComp значення за замовчуванням. Код установки значень за замовчуванням можна винести в спеціальний метод компонента, скажімо, SetDefaults, який можна буде викликати як з конструктора, так і безпосередньо. Зауважу, що при виклику процедури CopyComponent з CloneComponent такої проблеми не буде, оскільки в процедурі CloneComponent новий компонент спочатку конструюється (і його властивості набувають значення за умовчанням), а потім у нього копіюються дані з вихідного SrcComp-компонента.


Збереження даних об'єкта в базі даних


Ще один приклад застосування потокової системи – збереження даних об'єкта в memo-поле запису таблиці. Більшість реляційних бази даних не підтримує пряме збереження даних об'єктів в таблиці, але цю функціональність можна реалізувати самому. Щоб зберегти дані об'єкта в memo-поле необхідно перетворити їх у потік байтів, а потім помістити в Memo-поле. Ось процедури збереження і відновлення даних об'єкта в Memo-поле:



/ / Записує компонент в Blob-поле
procedure ComponentToBlobField (Instance: TComponent; AField: TBlobField);
var
S: TMemoryStream;
begin
S := TMemoryStream.Create;
try
/ / Зберегти дані компонента в потоці
S.WriteComponent(Instance);
S.Position := 0;
/ / Зберегти дані потоку в Blob-поле
AField.LoadFromStream(S);
finally
S.Free;
end;
end;


/ / Читає дані компонента з Blob-поля
procedure BlobFieldToComponent (AField: TBlobField; Instance: TComponent);
var
S: TMemoryStream;
begin
S := TMemoryStream.Create;
try
/ / Зберегти дані Blob-поля в потоці
AField.SaveToStream(S);
S.Position := 0;
/ / Прочитати дані компонента з потоку
S.ReadComponent(Instance);
finally
S.Free;
end;
end;


Передача даних об'єкта між процесами


Наступний приклад використання потокової системи – передача даних об'єкта між процесами.

Передачу даних об'єкта найпростіше здійснити, використовуючи механізм Automation. Великі масиви бінарної (несімвольной) інформації прийнято передавати серверу Automation за допомогою параметрів типу OleVariant. Наприклад, передачу даних об'єкта серверу Automation можна виконати так: викликає сторона упаковує дані об'єкта в OleVariant і передає їх серверу Automation, а приймаюча сторона, отримує дані і витягує їх з OleVariant.

Упаковкою даних об'єкта в OleVariant займається процедура ComponentToVariant, а витяганням даних – процедура VariantToComponent:



/ / Упаковка компонента в Variant
function ComponentToVariant(Instance: TComponent): OleVariant;
var
S: TMemoryStream;
varData: Pointer;
begin
S := TMemoryStream.Create;
try
/ / Зберегти компонент в потоці
S.WriteComponent(Instance);
/ / Створити Variant потрібної місткості
Result := VarArrayCreate([0, S.Size – 1], varByte);
varData := VarArrayLock(Result);
try
S.Position := 0;
/ / Копіювати дані з потоку в Variant
S.Read(VarData^, S.Size);
finally
VarArrayUnlock(Result);
end;
finally
S.Free;
end;
end;


/ / Витяг компонента з Variant
procedure VariantToComponent (const V: OleVariant; Instance: TComponent);
var
S: TMemoryStream;
P: Pointer;
begin
P := VarArrayLock(V);
try
S := TMemoryStream.Create;
try
/ / Копіювати дані з Variant в потік
S. Write (P ^, VarArrayHighBound (V, 1) – VarArrayLowBound (V, 1) + 1);
S.Position := 0;
/ / Читати компонент з потоку
S.ReadComponent(Instance);
finally
S.Free;
end;
finally
VarArrayUnlock(V);
end;
end;

Передача даних об'єкта між процесами знаходить застосування в багатокомпонентних додатках, де окремі підсистеми можна позначити або як клієнтські або як серверні. Як правило, в таких системах, серверна (викликається) сторона відповідає за керований доступ до даних, а клієнтська (викликає) сторона відповідає за модифікацію цих даних.


Читання даних форми з файлу


Використання потокового системи для реалізації механізму передачі даних об'єкта між процесами дійсно здається екзотичним, оскільки основне призначення потокової системи – читання-запис даних форми. Наступний приклад навряд чи можна зарахувати до розряду екзотичних, в ньому потокове система якраз використовується для читання даних форми з файлу.
Читання даних з файлу може стати в нагоді, наприклад, коли різним користувачам потрібно надавати різні зовнішні подання форми. Таке завдання може виникнути, якщо форма має велике число полів, і одночасний показ всіх полів неможливий або серйозно ускладнює роботу користувача.

Найпростіший спосіб вирішення проблеми – постачати користувачеві стандартну форму і кілька додаткових форм. Універсальне рішення – постачати конструктор форм, за допомогою якого користувач самостійно додасть потрібні поля і видалить непотрібні.
Стандартну форму можна зберігати як ресурс програми, а додаткові форми або форми, сконструйовані користувачем, можна зберігати в зовнішньому сховищі (у файлі, в Memo-поле бази даних, в потоці структурованого сховища і т.д.)

Дані форми можна напряму записати в потік методом TStream.WriteComponentRes. Невеликі труднощі виникають при читанні даних форми з потоку; щоб коректно прочитати форму з потоку доведеться запозичити код TApplication.CreateForm



/ / Форма повинна бути записана в потік за допомогою процедури TStream.WriteComponentRes
/ / За допомогою цієї процедури не можна конструювати
// Application.MainForm
procedure CreateFormFromStream (AOwner: TComponent; InstanceClass: TComponentClass;
var Reference; Stream: TStream);
var
Instance: TComponent;
dm: TDataModule;
fm: TCustomForm;
begin
Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
try
if Instance is TDataModule then
begin
dm := TDataModule(Instance);
dm.CreateNew(AOwner);
Stream.ReadComponentRes(dm);
end
else
if Instance is TCustomForm then
begin
fm := TCustomForm(Instance);
fm.CreateNew(AOwner);
Stream.ReadComponentRes(fm);
end;
except
Instance.Free;
TComponent(Reference) := nil;
raise;
end;
end;

Дані форми, як і дані будь-якого іншого об'єкта, породженого від класу TComponent, можна записати в потік і прочитати з потоку. Процедури запису і читання даних об'єкта надає сама потокове система.


Підтримка сервісів перманентності в COM-об'єктах


На відміну від об'єктів Delphi, COM-об'єкти повинні самостійно реалізовувати сервіси запису і читання власних даних. Клієнти COM-об'єктів отримують доступ до цих сервісів (ще їх називають сервісами перманентності [4]) через один з інтерфейсів сімейства IPersist * (IPersistFile, IPersistMemory, IPersistStream, IPersistStorage, IPersistMoniker). Якщо ваш COM-об'єкт повинен підтримувати один з цих інтерфейсів, то для їх реалізації також можна скористатися потокової системою VCL.

Розглянемо реалізацію сервісів перманентності на прикладі інтерфейсу IPersistStream. Дані COM-об'єкта ми будемо зберігати в спеціальному компоненті, а реалізацію методів інтерфейсу IPersistStream делегуємо допоміжному класу TOlePersist, у якого буде посилання на компонент, який зберігає дані COM-об'єкта. При такому підході клас TOlePersist можна буде використовувати в різних реалізаціях COM-об'єктів.

Найбільший інтерес в інтерфейсі IPersistStream представляють методи IPersistStream.Load і IPersistStream.Save. Їх реалізація наводиться нижче:



function TolePersist.PersistStreamLoad (const stm: IStream): HResult;
var
Temp: TComponent;
S: TOleStream;
SavePosition: Integer;
begin
if stm = nil then
begin
Result := E_POINTER;
Exit;
end;
try
S := TOleStream.Create(stm);
try
/ / Переконатися в тому, що компонент можна прочитати
SavePosition := S.Position;
Temp := TComponentClass(fComponent.ClassType).Create(nil);
try
S.ReadComponent(Temp);
finally
Temp.Free;
S.Position := SavePosition;
end;
/ / Тепер можна прочитати сам компонент
S.ReadComponent(fComponent);
finally
S.Free;
end;
Result := S_OK;
except
Result := E_UNEXPECTED;
end;
end;


function TolePersist.PersistStreamSave (const stm: IStream; fClearDirty: BOOL): HResult;
var
S: TOleStream;
begin
if stm = nil then
begin
Result := E_POINTER;
Exit;
end;
try
S := TOleStream.Create(stm);
try
S.WriteComponent(fComponent);
finally
S.Free;
end;
if fClearDirty then SetModified(False);
Result := S_OK;
except
Result := E_UNEXPECTED;
end;
end;

Реалізацію інших методів інтерфейсу IPersistStream ви можете знайти в демонстраційній програмі, яку можна завантажити сайту www.programme.ru. Там же ви зможете знайти і код інших прикладів, описаних в статті.


Висновок


У використанні потокової системи немає нічого надскладного або незвичайного, мабуть, один з доказів цього – невеликий обсяг прикладів. Можливо, самі приклади різні, але всі вони в своїй основі використовують одну ідею – представлення програмних даних у вигляді потоку байтів.

Найчастіше програмні дані зберігаються (інкапсулюються) в об'єкті, тому в якості вхідного параметра всіх процедур використовується об'єкт-компонент. Якщо ви будете писати власні процедури, рекомендую робити саме так, тоді ваші процедури будуть відповідати вимогу спільності та універсальності.

Якщо ви ще не знайомі з потокової системою, раджу звернути увагу на літературу, наведену в бібліографії, зокрема на дві книги Рея Лішнера [2] і [3], в них міститься інформація в достатній обсязі. Тут я тільки побіжно згадав особливості самої потокової системи, цій темі потрібно присвячувати окрему статтю, а, краще цілу серію статей. Сподіваюся, моя стаття підштовхне інших програмістів поділитися знаннями.

Застосовуючи потокову систему починаючи з першої версії Delphi, я продовжую відкривати для себе її нові можливості, і недавно виявив

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Метки: , , , , , ,
Рубрики: Комерція

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*