Ø TReader まず、Delphi プロジェクト ファイルを確認すると、次のような数行のコードが見つかります。 begin application.Initialize; Application.CreateForm(TForm1, Form1); これが Delphi プログラムの入り口です。 。これらのコード行の意味について簡単に説明します。 Application.Initialize は実行中のアプリケーションに必要な初期化作業を実行し、Application.CreateForm(TForm1, Form1) は必要なフォームを作成し、Application.Run プログラムが実行を開始してメッセージを入力します。サイクル。 今一番気になるのはフォーム作成の文章です。フォームとフォーム上のコンポーネントはどのように作成されますか?前述したように、フォーム自体のプロパティを含むフォーム内のすべてのコンポーネントは、DFM ファイルに含まれます。Delphi はプログラムをコンパイルするときに、コンパイル命令 {$R *.dfm} を使用して DFM ファイル情報をコンパイルします。実行可能ファイルに入れます。したがって、フォームを作成するときに DFM 情報を読み取る必要があると結論付けることができます。それを読み取るには、もちろん TReader を使用します。 プログラムを段階的に追跡すると、プログラムがフォームの作成プロセス中に TReader の ReadRootComponent メソッドを呼び出していることがわかります。このメソッドの機能は、ルート コンポーネントとそれが所有するすべてのコンポーネントを読み取ることです。このメソッドの実装を見てみましょう: function TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature; Result := nil; // ストリームからのロードにより名前空間が追加されます try try ReadPRefix; (Flags, I ); Root = nil の場合、 Result := TComponentClass(FindClass(ReadStr)).Create(nil); ReadStr; end else begin Result := Root; { クラス名を無視 } if csDesigning in Result.ComponentState then ReadStr else begin Include(Result.FComponentState, csReading); FindUniqueName(ReadStr) 終了; TClassFinder.Create(TPersistentClass(Result.ClassType), True); try FLookupRoot := G := GlobalLoaded; if G <> nil then FLoaded := G else FLoaded := TList.Create if FLoaded.IndexOf(FRoot) ) < 0 の場合 FLoaded.Add(FRoot); Include(FRoot.FComponentState, csLoading); Include(FRoot.FComponentState, csReading); G = nil の場合、FLoaded.Count - 1最後に TComponent(FLoaded[I]).Loaded を実行します。 G = nil の場合は FLoaded.Free := nil; end; 最後に FFinder.Free; end; ... 最後に、ReadRootComponent は Filer オブジェクト タグ ('TPF0') を呼び出します。オブジェクトをロードする前にタグを検出することで、無効なデータや古いデータを誤って読み取ることを防ぎます。 ReadPrefix(Flags, I) という文をもう一度見てください。ReadPrefix メソッドの機能は、ストリーム内のコンポーネントの前にあるフラグ (PreFix) を読み取ることを除いて、ReadSignature の機能と非常に似ています。 Write オブジェクトがコンポーネントをストリームに書き込むとき、コンポーネントの前に 2 つの値が事前に書き込まれます。最初の値は、コンポーネントが祖先フォームから継承されたフォームであるかどうか、およびフォーム内のその位置が重要フラグであるかどうかを示します。 ; 2 番目の値は、祖先フォームが作成された順序を指定します。 次に、Root パラメータが nil の場合は、ReadStr によって読み取られたクラス名を使用して新しいコンポーネントを作成し、コンポーネントの Name 属性をストリームから読み取ります。それ以外の場合は、クラス名を無視して、Name 属性の一意性を判断します。 FRoot.ReadState(Self); これは非常に重要な文です。ReadState メソッドは、ルート コンポーネントとその所有コンポーネントのプロパティを読み取ります。この ReadState メソッドは TComponent のメソッドですが、さらに追跡すると、実際には TReader の ReadDataInner メソッドが見つかることがわかります。このメソッドの実装は次のとおりです。procedure TReader.ReadDataInner(Instance: TComponent);var OldParent, OldOwner: TComponent。 ;EndOfList ではないときに ReadListEnd を開始します。OldParent := Parent := Instance.GetChildParent; Assigned(Owner) でない場合は、ReadComponent(nil) を実行します。次のコード行があります: EndOfList ではないときに ReadProperty(Instance);これは、ルート コンポーネントのプロパティを読み取るために使用されます。前述したように、コンポーネント自体の公開プロパティと、TTimer の Left や Top などの非公開プロパティの両方があります。これら 2 つの異なるプロパティには、2 つの異なる読み取りメソッドが必要です。この考えを検証するために、ReadProperty メソッドの実装を見てみましょう。プロシージャ TReader.ReadProperty(AInstance: TPersistent);…begin… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); if PropInfo <> nil then ReadPropValue(Instance, PropInfo) else begin { 定義されたプロパティのエラーから確実に回復できませんFCanHandleExcepts := False; Instance.DefineProperties(Self); FCanHandleExcepts := True; if FPropName <> '' then PropertyError(FPropName) end; 以下の説明は、FPropName です。ファイルの属性名から読み取ります。 PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); このコードは、公開された属性 FPropName の情報を取得します。次のコードからわかるように、属性情報が空でない場合、属性値は ReadPropValue メソッドを通じて読み取られ、ReadPropValue メソッドは RTTI 関数を通じて属性値を読み取りますが、ここでは詳しく説明しません。属性情報が空の場合は、属性 FPropName が未公開であることを意味し、別のメカニズムを通じて読み取る必要があります。これは、次のように、前述の DefineProperties メソッドです: Instance.DefineProperties(Self); このメソッドは実際に TReader の DefineProperty メソッドを呼び出します: プロシージャ TReader.DefineProperty(const Name: string; ReadData: TReaderProc; WriteData: TWriterProc; HasData: Boolean) ; SameText(Name, FPropName) および Assigned(ReadData) の場合に開始します。 begin ReadData(Self); end;end; まず、読み取り属性名がプリセット属性名と同じかどうかを比較し、読み取りメソッド ReadData が空でない場合は、ReadData メソッドを実行します。属性値を読み取るために呼び出されます。 これで、ルート コンポーネントが読み取られました。次のステップは、ルート コンポーネントが所有するコンポーネントを読み取ることです。メソッドを見てみましょう:procedure TReader.ReadDataInner(Instance: TComponent); このメソッドの後に次のようなコードがあります: while not EndOfList do ReadComponent(nil); これはサブコンポーネントを読み取るために使用されます。サブコンポーネントの読み取りメカニズムは、上で紹介したルート コンポーネントの読み取りと同じであり、ツリーを深く走査します。 ここまで、コンポーネント読み取りメカニズムを紹介しました。 コンポーネントの書き込みメカニズムを見てみましょう。コンポーネントをフォームに追加すると、その関連プロパティが DFM ファイルに保存されます。このプロセスは TWriter によって完了します。 Ø TWriter TWriter オブジェクトは、ストリームにデータを書き込むインスタンス化可能な Filer オブジェクトです。 TWriter オブジェクトは、TFiler から継承したメソッドをオーバーライドするだけでなく、さまざまなデータ型 (Integer、String、Component など) を書き込むための多数のメソッドも追加します。 TWriter オブジェクトは、さまざまなタイプのデータをストリームに書き込むためのメソッドを多数提供します。TWrite オブジェクトは、さまざまなデータに基づいてさまざまな形式でデータをストリームに書き込みます。 したがって、TWriter オブジェクトの実装および適用メソッドを習得するには、Writer オブジェクトがデータを格納する形式を理解する必要があります。 まず注意すべきことは、各 Filer オブジェクトのストリームには Filer オブジェクト タグが含まれていることです。このタグは 4 バイトを占め、その値は「TPF0」です。 Filer オブジェクトは、WriteSignature メソッドと ReadSignature メソッドのタグにアクセスします。このラベルは主に Reader オブジェクトがデータ (コンポーネントなど) を読み取る際の読み取り操作をガイドするために使用されます。 次に、Writer オブジェクトは、データを保存する前に、後で保存するデータの種類を示すバイト フラグを残す必要があります。このバイトは、TValueType 型の値です。 TValueType は 1 バイトのスペースを占める列挙型で、次のように定義されます。 TValueType = (VaNull, VaList, VaInt8, VaInt16, VaInt32, VaEntended, VaString, VaIdent, VaFalse, VaTrue, VaBinary, VaSet, VaLString, VaNil, VaCollection);したがって、Writer オブジェクトのすべてのデータ書き込みメソッドでは、実装上、最初にフラグ ビットを書き込んでから対応するデータを書き込む必要があり、Reader オブジェクトのすべてのデータ読み取りメソッドでは、最初にフラグ ビットを読み取って判断する必要があります。読み取りデータと一致する場合、読み取りデータが無効であることを示す例外イベントが生成されます。 VaList フラグには特別な目的があり、後で同じタイプの項目が連続することを識別するために使用され、連続する項目の終わりを識別するフラグは VaNull です。したがって、Writer オブジェクトが複数の連続する同一の項目を書き込む場合、最初に WriteListBegin で VaList フラグを書き込み、次にデータ項目を書き込んだ後に VaNull フラグを書き込みます。これらのデータを読み取るときは、ReadListBegin で開始し、ReadListEnd で終了し、中間の EndofList 関数は、VaNull フラグがあるかどうかを判断します。 TWriter の非常に重要なメソッドを見てみましょう: WriteData:procedure TWriter.WriteData(Instance: TComponent);... begin... WritePrefix(Flags, FChildPos); if UseQualifiedNames then WriteStr(GetTypeData(PTypeInfo(Instance.ClassType) .ClassInfo)) .UnitName + '.' + Instance.ClassName) else WriteStr(Instance.ClassName); WriteStr(Instance.Name); if (FAncestorList <> nil) and (FAncestorPos < FAncestorList.Count) then begin if Ancestor <> nil then Inc(FChildPos); ); WriteListEnd; ...end; WriteData メソッドから、DFM ファイル情報の生成の概要がわかります。まずコンポーネントの前にフラグ(PreFix)を記述し、次にクラス名とインスタンス名を記述します。このステートメントの直後: WriteProperties(Instance); これは、コンポーネントのプロパティを書き込むために使用されます。前述したように、DFM ファイルには公開属性と非公開属性が存在し、これら 2 つの属性の記述方法は異なる必要があります。 WriteProperties の実装を見てみましょう: プロシージャ TWriter.WriteProperties(Instance: TPersistent);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount; if Count > 0 then begin GetMem(PropList, Count * SizeOf(Pointer) )); GetPropInfos(Instance.ClassInfo, PropList); for I := 0 から Count - 1 を試してくださいPropInfo := PropList^[I]; PropInfo = nil の場合は Break; If IsStoredProp(Instance, PropInfo) end; 最後に FreeMem(PropList, Count * SizeOf(Pointer)); Instance.DefineProperties(Self);end; 次のコードを参照してください。 PropInfo) then WriteProperty(Instance, PropInfo); 関数 IsStoredProp は、ストレージ修飾子を使用してプロパティを保存する必要があるかどうかを判断し、WriteProperty を呼び出してプロパティを保存します。WriteProperty は一連の RTTI を通じて実装されます。機能。公開されたプロパティを保存した後、非公開のプロパティを次のコードで保存する必要があります。 Instance.DefineProperties(Self); TTimer の Left プロパティと Top プロパティは前に説明しました。 。 さて、ここまででまだ疑問が残ります。ルート コンポーネントが所有するサブコンポーネントはどのように保存されるのでしょうか? WriteData メソッドを見てみましょう (このメソッドは前に説明しました)。 begin if (FAncestor が TComponent) かつ (csInline in TComponent(FAncestor).ComponentState) then FRootAncestor := TComponent(FAncestorList) := TList.Create; TComponent(FAncestor).GetChildren(AddAncestor, FRootAncestor); if csInline in Instance.ComponentState then FRoot := Instance.GetChildren(WriteComponent, FRoot);無料; 終了; 終了; IgnoreChildren 属性を使用すると、Writer オブジェクトは、コンポーネントが所有する子コンポーネントを保存せずにコンポーネントを保存できます。 IgnoreChildren プロパティが True の場合、Writer オブジェクトは、所有する子コンポーネントを保存せずにコンポーネントを保存します。それ以外の場合は、サブコンポーネントを保存する必要があります。 Instance.GetChildren(WriteComponent, FRoot); これは、サブコンポーネントを作成する場合に最も重要な文であり、ルート コンポーネント FRoot にサブコンポーネントがある場合、WriteComponent メソッドをコールバック関数として使用します。 、WriteComponent を使用してサブコンポーネントを保存します。このように、DFM ファイルにはツリー状のコンポーネント構造が表示されます。