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では、フォームのデザイン等(=つまりプロパティの設定値)を設計時(コンパイル時)にリソースに保存し、実行時にリソースからプロパティを復元して実行するメカニズムがある。 他の言語では、一般的には、シリアライズと呼ばれることがある)
Reading a component from a stream
LResources には次のような関数があります:
procedure ReadComponentFromBinaryStream(AStream: TStream; var RootComponent: TComponent; OnFindComponentClass: TFindComponentClassEvent; TheOwner: TComponent = nil);
- AStream is the stream containing a component in binary format.
- RootComponent is either an existing component, which data will be overwritten, or it is nil and a new component will be created.
- OnFindComponentClass is a function, that is used by TReader to get the class from the classnames in the stream. For example:
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 にはそのコンポーネントのオーナーとなります。
Streamable properties
There are some limitations, what types TReader/TWriter can stream:
- Base types can be streamed: string, integer, char, single, double, extended, byte, word, cardinal, shortint, method pointers, etc. .
- TPersistent and descendants can be streamed
- records, objects and classes not descending from TPersistent can not be streamed. To stream them you need to tell TReader/TWriter how. See below #Streaming custom Data - DefineProperties.
Streaming custom Data - DefineProperties
You can stream additinal arbitrary data by overriding DefineProperties. This allows to stream all data, that have no base types. For example to stream a variable FMyRect: TRect of your component, add the following three methods to your component:
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'.
If you stream a lot of TRect, then you probably do not want to write everytime ths code. The unit LResources contains an example how to write a procedure to define a rect property:
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
Now the example can be extended and we can use arbitrary properties with only a few lines of code:
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 コントロール を使用して、保存、読み込み利用ができます。これ以上、詳細なコードを書く必要はありません。
Writing and Reading components from/to XML
Streaming components is simple: See the example in lazarus/examples/xmlstreaming/.
結論
RTTI は強力なメカニズムです。これは全てのクラスでストリームを簡単に取り扱うことができ、また膨大で退屈な load/save コードの記述を回避するのに役立ちます。
こちらも参照してください。