Streaming components/ja
│
Deutsch (de) │
English (en) │
français (fr) │
日本語 (ja) │
polski (pl) │
português (pt) │
はじめに
通常、ハードディスクやネットワークストリームにデータを蓄えておく場合は、それらの特性に応じてロードやセーブ(読み込みや書き込み)のためのコードを書かなければなりません。このチュートリアルでは、余分な load/save コードを書くことなく、RTTI を使用して、ストリームから読み込みや書き込みを行うクラスの書き方について述べます。
lazarus コードでのサンプルや TCheckBox を含んだ TGroupBox をストリームに保存したり、両方のコンポーネントのコピーを作成してストリームから戻すといったデモを説明します。
<lazaruspath>/examples/componentstreaming/ を参照してください。
RTTI コントロールとのコンビネーションで、GUI と ディスク/ネットワークとの間でプログラムデータをやり取りする際に必要なコードの量を最小限にすることができます。
TComponent / TPersistent
TPersistent クラスは、Classes ユニット内で定義され、{$M+}というコンパイラスイッチで使用されます。このスイッチは定義されたクラスに対して、 Run Time Type Information(RTTI)(訳注:実行時型情報)を付加するコンパイラ指令です。 つまり、TPersistentクラスと、その派生クラスは、実行時型情報をもつクラス群として機能することを意味します。'Published' のプロパティは'public'のプロパティと同様に使用することができますが、実行時型情報が付加されていることで、これらのクラスに対しては、実行時にプロパティへの動的なアクセスが可能になります。
すべてのPublished のプロパティが、読み書きできるだけでなく、実行時に、どのような名前で、どのような型になっているかをリストアップすることができます。 IDE では、ユーザーが生成したコンポーネントを、(今つくられたコンポーネントがどのようなものか、過去につくられたIDEが知る由もありませんが、しかし..)IDEがそのクラスをプロパティを通して自由に扱えるようにするための優れた機能です。
TComponentはTPersistentから派生し、子コンポーネントを持てるようになっています。これはストリーミングで大変重要な事項です。1つのコンポーネントがルートコンポーネントになると、子コンポーネントのリストからlookup rootによって参照されます。
(訳注:1つのクラスがストリーミング or シリアライズされる時は、メモリ上での参照問題をなんらかの形で解決する必要がある。ルートはストリーミングの基点となるクラスであり、参照解決の重要な1つである。)
TReader / TWriter
TReaderとTWriterは、ストリームにたいしてTComponentを読み書きするためのワーカークラスです。(訳注:ストリームクラスにたいして補助的に便利な関数の集まりになっています。) CreateLRSReaderとCreateLRSWriterを見てください。 これらは、特別なフォーマットを読み書きする場合Driverを使います。 このとき、LResourcesユニットで定義されるバイナリオブジェクトフォーマットのためのreader(TLRSObjectReader)とwriter(TLRSObjectWriter)があり、Laz_XMLStreamingで定義されるTDOMDocumentのためのwriter (TXMLObjectWriter)があります。
LResourcesユニットは、バイナリフォーマットからテキストに変換する関数とその逆の関数を含みます。 (LRSObjectBinaryToText, LRSObjectTextToBinary). DelphiではWidestringでしたが、LCLは文字列に対してはUTF8を好みます。 そのための変換関数も同様にいくつかあります。
独自コンポーネントの書き方 - Part 1
カスタムコンポーネントは次のようにシンプルです:
type TMyComponent = class(TComponent) private FID: integer; published property ID: integer read FID write FID; end;
ストリームにコンポーネントを書き出す
LResources ユニットには次のような関数があります:
procedure WriteComponentAsBinaryToStream(AStream: TStream; AComponent: TComponent);
これは、コンポーネントをバイナリフォーマットでストリームに書き出します。
たとえば次のようにします:
procedure TForm1.Button1Click(Sender: TObject); var AStream: TMemoryStream; begin AStream:=TMemoryStream.Create; try WriteComponentAsBinaryToStream(AStream,AGroupBox); ... save stream somewhere ... finally AStream.Free; end; end;
(訳注:この例では、メモリ上にグループボックスのすべてのプロパティがバイナリ形式で保管される。 一般的には、バッファとして使う以外には、メモリ上に入れる例は少ない。ファイルストリームをつかって、それをファイルに保存して、後日読み出したり、ソケットストリームをつかって、ソケットで別のマシンに転送したりして、オブジェクトの状態を「保存」「転送」することができる。完全に保存するためには、オブジェクトの状態がすべてプロパティでpublishされていなくてはならないことに注意しよう。 Lazarusでは、フォームのデザイン等(=つまりプロパティの設定値)を設計時(コンパイル時)にリソースに保存し、実行時にリソースからプロパティを復元して実行するメカニズムがある。 他の言語では、一般的には、シリアライズと呼ばれることがある)
ストリームからコンポーネントを読み出す
LResources には次のような関数があります:
procedure ReadComponentFromBinaryStream(AStream: TStream; var RootComponent: TComponent; OnFindComponentClass: TFindComponentClassEvent; TheOwner: TComponent = nil);
- AStream はコンポーネントをバイナリフォーマットで含んでいるストリームを指定します。
- RootComponent は存在しているコンポーネントを指定した場合はそのプロパティは上書きされます。nilを指定した場合は、新しいコンポーネントがストリームから生成されます。
- OnFindComponentClass はTReaderによって、ストリーム内のクラス名から、クラスを取得するのに使われる、イベント関数です。(訳注:クラス参照型を取得することで、TReaderは名前文字列からそのクラスのコンストラクタを呼ぶことができる)
たとえば、次のように、書くことができます:
procedure TCompStreamDemoForm.OnFindClass(Reader: TReader; const AClassName: string; var ComponentClass: TComponentClass); begin if CompareText(AClassName,'TGroupBox')=0 then ComponentClass:=TGroupBox else if CompareText(AClassName,'TCheckBox')=0 then ComponentClass:=TCheckBox; end;
- コンポーネントを新規に生成した場合、Owner にはそのコンポーネントのオーナーとなります。
ストリーム可能なプロパティについて
TReaderやTWriterがストリームできるプロパティには、いくつかの条件があります。
- 基本型はストリームできます。すなわち、integer, char, single, double, extended, byte, word, cardinal, shortint, method pointers, などなど. .
- TPersistent とその派生クラスはストリームできます。
- 構造体、object型、TPersistentから派生しないクラス型は、ストリームできません。
それらをストリームするには、TReader/TWriterにその方法を教える必要があります。次を見てください。#Streaming custom Data - DefineProperties.
カスタムデータをストリームする - DefineProperties
DefinePropertiesをオーバーライドすることで、通常ではストリームできないデータをストリームすることができるようになります。この方法は、すべてのデータに対して、たとえ基本型をもたないものでも、ストリームすることを可能にします。たとえば、あなたが作ったコンポーネントの変数 FMyRect: TRectをストリームしたい場合、次のような3つのメソッドをコンポーネントに加えます。
procedure DefineProperties(Filer: TFiler); override; procedure ReadMyRect(Reader: TReader); procedure WriteMyRect(Writer: TWriter);
続けて次のように実装してください:
procedure TMyComponent.DefineProperties(Filer: TFiler); var MyRectMustBeSaved: Boolean; begin inherited DefineProperties(Filer); MyRectMustBeSaved:=(MyRect.Left<>0) or (MyRect.Top<>0) or (MyRect.Right<>0) or (MyRect.Bottom<>0); Filer.DefineProperty('MyRect',@ReadMyRect,@WriteMyRect,MyRectMustBeSaved); end; procedure TMyComponent.ReadMyRect(Reader: TReader); begin with Reader do begin ReadListBegin; FMyRect.Left:=ReadInteger; FMyRect.Top:=ReadInteger; FMyRect.Right:=ReadInteger; FMyRect.Bottom:=ReadInteger; ReadListEnd; end; end; procedure TMyComponent.WriteMyRect(Writer: TWriter); begin with Writer do begin WriteListBegin; WriteInteger(FMyRect.Left); WriteInteger(FMyRect.Top); WriteInteger(FMyRect.Right); WriteInteger(FMyRect.Bottom); WriteListEnd; end; end;
This will save MyRect as a property 'MyRect'. これで、MyRectをプロパティとして保存できます。 しかし、もし、たくさんのTRectをストリームする必要がある時、このようなコードで何度も書きたくないでしょう。 LResourcesユニットには、rectプロパティを定義する手続きを書く例があります。
procedure DefineRectProperty(Filer: TFiler; const Name: string; ARect, DefaultRect: PRect);
さきほどの方法はこれを使うと非常に短く記述することができます:
procedure TMyComponent.DefineProperties(Filer: TFiler); begin inherited DefineProperties(Filer); DefineRectProperty(Filer,'MyRect',@FMyRect,nil); end;
独自コンポーネント - Part 2
今回の例では、たった数行のコードで、クラスを拡張して任意のプロパティを使うことができるか、を示します。
type TMyComponent = class(TComponent) private FID: integer; FRect1: TRect; FRect2: TRect; protected procedure DefineProperties(Filer: TFiler); override; public property Rect1: TRect read FRect1 write FRect1; property Rect2: TRect read FRect2 write FRect2; published property ID: integer read FID write FID; end; procedure TMyComponent.DefineProperties(Filer: TFiler); begin inherited DefineProperties(Filer); DefineRectProperty(Filer,'Rect1',@FRect1,nil); DefineRectProperty(Filer,'Rect2',@FRect2,nil); end;
このコンポーネントは RTTI コントロール を使用して、保存、読み込み利用ができます。これ以上、詳細なコードを書く必要はありません。
コンポーネントをXMLへストリームする
XML形式でコンポーネントをストリームさせるのはとても簡単です。 lazarus/examples/xmlstreaming/ の例を見てください。
結論
RTTI は強力なメカニズムです。これは全てのクラスでストリームを簡単に取り扱うことができ、また膨大で退屈な load/save コードの記述を回避するのに役立ちます。
こちらも参照してください。 RTTI コントロール