Ø TReader 먼저 Delphi 프로젝트 파일을 살펴보면 다음과 같은 몇 줄의 코드를 찾을 수 있습니다. start application.Initialize; Application.CreateForm(TForm1, Form1); 이것이 Delphi 프로그램의 입구입니다. . 다음 코드 줄의 의미에 대해 간략하게 설명하겠습니다. Application.Initialize는 실행 중인 응용 프로그램에 필요한 일부 초기화 작업을 수행하고, Application.CreateForm(TForm1, Form1)은 필요한 양식을 생성하며, Application.Run 프로그램은 실행을 시작하고 메시지를 입력합니다. 주기. 지금 우리가 가장 고민하는 것은 형태를 만드는 문장이다. 양식과 양식의 구성 요소는 어떻게 생성되나요? 앞서 언급한 바와 같이, 폼 자체의 속성을 포함하여 폼의 모든 구성 요소는 DFM 파일에 포함됩니다. 델파이는 프로그램을 컴파일할 때 컴파일 명령 {$R *.dfm}을 사용하여 DFM 파일 정보를 컴파일합니다. 실행 파일에 넣습니다. 따라서 양식을 작성할 때 DFM 정보를 읽어야 한다는 결론을 내릴 수 있습니다. 물론 TReader입니다! 프로그램을 단계별로 추적하면 프로그램이 양식을 생성하는 과정에서 TReader의 ReadRootComponent 메서드를 호출하는 것을 알 수 있습니다. 이 메서드의 기능은 루트 구성 요소와 해당 구성 요소가 소유한 모든 구성 요소를 읽는 것입니다. 이 메서드의 구현을 살펴보겠습니다. function TReader.ReadRootComponent(Root: TComponent): TComponent;...begin ReadSignature; Result := nil; 이름 공간에 추가하는 중 ReadPRefix를 시도하세요. (플래그, I ); Root = nil이면 시작 Result := TComponentClass(FindClass(ReadStr)).Create(nil) := ReadStr; end else start Result := ReadStr; { 클래스 이름 무시 } if Result.ComponentState then ReadStr else start include(Result.FComponentState, csReading); FindUniqueName(ReadStr); end; FRoot := 결과; TClassFinder.Create(TPertantClass(Result.ClassType), True); FLookupRoot 시도 := Result; G := GlobalLoaded; if G <> nil then FLoaded := G else FLoaded := TList.Create; ) < 0이면 FLoaded.Add(FRoot) FOwner := FRoot; include(FRoot.FComponentState, csReading); FRoot.ReadState(Self); G = nil이면 I := 0에서 FLoaded.Count - 1 do TComponent(FLoaded[I]).Loaded; 마지막으로 G = nil이면 FLoaded.Free; nil; 마지막으로 FFinder.Free; end; 마지막으로 GlobalNameSpace.EndWrite end;end; 먼저 ReadSignature를 호출하여 파일러 개체 태그('TPF0')를 읽습니다. 객체를 로드하기 전에 태그를 감지하면 유효하지 않거나 오래된 데이터를 실수로 읽는 것을 방지할 수 있습니다. ReadPrefix(Flags, I) 문장을 다시 살펴보세요. ReadPrefix 메서드의 기능은 스트림의 구성 요소 앞에 있는 플래그(PreFix)를 읽는다는 점을 제외하면 ReadSignature의 기능과 매우 유사합니다. Write 객체는 스트림에 컴포넌트를 쓸 때 컴포넌트 앞에 두 개의 값을 미리 씁니다. 첫 번째 값은 해당 컴포넌트가 상위 폼에서 상속된 폼인지, 폼에서의 해당 위치가 중요 플래그인지 여부를 나타냅니다. ; 두 번째 값은 상위 양식이 생성된 순서를 지정합니다. 그런 다음 Root 매개 변수가 nil이면 ReadStr에서 읽은 클래스 이름으로 새 구성 요소를 만들고 스트림에서 구성 요소의 Name 속성을 읽습니다. 그렇지 않으면 클래스 이름을 무시하고 Name 속성의 고유성을 확인합니다. FRoot.ReadState(Self); 이는 매우 중요한 문장입니다. ReadState 메서드는 루트 구성 요소와 해당 구성 요소의 속성을 읽습니다. 이 ReadState 메서드는 TComponent의 메서드이지만 추가 추적을 통해 실제로 TReader의 ReadDataInner 메서드를 찾는다는 사실을 알 수 있습니다. 절차 TReader.ReadDataInner(Instance: TComponent);var OldParent, OldOwner: TComponent ;EndOfList가 아닌 동안 시작 do ReadProperty(Instance); OldParent := OldOwner := Instance.GetChildParent; 할당되지 않은 경우 Owner:= Root; ReadComponent(nil); 마지막으로 Parent := OldOwner; 다음 코드 줄이 있습니다: while not EndOfList do ReadProperty(Instance); 이는 루트 구성 요소의 속성을 읽는 데 사용됩니다. 앞서 언급한 것처럼 구성 요소 자체의 게시된 속성과 TTimer의 Left 및 Top과 같은 게시되지 않은 속성이 모두 있습니다. 이러한 두 가지 다른 속성에는 두 가지 다른 읽기 방법이 있어야 합니다. 이 아이디어를 확인하기 위해 ReadProperty 메서드의 구현을 살펴보겠습니다. 절차 TReader.ReadProperty(AInstance: TPersistant);…begin… PropInfo := GetPropInfo(Instance.ClassInfo, FPropName); if PropInfo <> nil then ReadPropValue(Instance, PropInfo) else start { 정의된 속성의 오류에서 안정적으로 복구할 수 없습니다. } FCanHandleExcepts := False; Instance.DefineProperties(Self); FCanHandleExcepts := True; if FPropName <> '' then PropertyError(FPropName); ...end; 다음은 설명입니다. 파일 속성 이름에서 읽습니다. 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)인 경우 시작하고 그런 다음 start ReadData(Self); = ''; end;end; 먼저 읽기 속성 이름이 미리 설정된 속성 이름과 동일한지 비교하고 읽기 메서드 ReadData가 비어 있지 않은지 확인합니다. 속성 값을 읽기 위해 호출됩니다. 좋습니다. 루트 구성 요소를 읽었으며 다음 단계는 루트 구성 요소가 소유한 구성 요소를 읽는 것입니다. 메소드를 살펴보겠습니다: Procedure TReader.ReadDataInner(Instance: TComponent); 이 메소드 뒤에는 다음과 같은 코드가 있습니다: while not EndOfList do ReadComponent(nil); 이것은 하위 구성요소를 읽는 데 사용됩니다. 하위 구성 요소를 읽는 메커니즘은 위에서 소개한 루트 구성 요소를 읽는 것과 동일합니다. 이는 트리를 심층적으로 탐색하는 것입니다. 지금까지 구성요소 읽기 메커니즘이 도입되었습니다. 컴포넌트의 쓰기 메커니즘을 살펴보겠습니다. 양식에 구성 요소를 추가하면 관련 속성이 DFM 파일에 저장됩니다. 이 프로세스는 TWriter에 의해 완료됩니다. Ø TWriter TWriter 개체는 스트림에 데이터를 쓰는 인스턴스화 가능한 Filer 개체입니다. TWriter 객체는 TFiler에서 직접 상속됩니다. 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);... start... WritePrefix(Flags, FChildPos); if UseQualifiedNames then WriteStr(GetTypeData(PTypeInfo(Instance.ClassType) .ClassInfo)) .UnitName + '.' + Instance.ClassName) else WriteStr(Instance.ClassName); WriteStr(Instance.Name); PropertiesPosition := Position; if (FAncestorList <> nil) and (FAncestorPos < FAncestorList.Count) then start if Ancestor <> nil then Inc(FChildPos); ); WriteListEnd; ...end; WriteData 메소드에서 DFM 파일 정보 생성 개요를 볼 수 있습니다. 먼저 컴포넌트 앞에 플래그(PreFix)를 쓴 다음 클래스 이름과 인스턴스 이름을 씁니다. 다음 문 바로 다음은 WriteProperties(Instance)입니다. 이는 구성 요소의 속성을 쓰는 데 사용됩니다. 앞서 언급했듯이 DFM 파일에는 게시된 속성과 게시되지 않은 속성이 모두 있습니다. 이 두 속성의 작성 방법은 달라야 합니다. WriteProperties의 구현을 살펴보겠습니다. 프로시저 TWriter.WriteProperties(Instance: TPersistant);...begin Count := GetTypeData(Instance.ClassInfo)^.PropCount; if Count > 0 then start GetMem(PropList, Count * SizeOf(Pointer) )); I에 대해 GetPropInfos(Instance.ClassInfo, PropList)를 시도하십시오:= 0 - 1 시작 PropInfo := PropList^[I]; if PropInfo = nil; if IsStoredProp(Instance, PropInfo) then WriteProperty(Instance, PropInfo) end; Instance.DefineProperties(Self);end; 다음 코드를 참조하십시오: if IsStoredProp(Instance, PropInfo) 그런 다음 WriteProperty(Instance, PropInfo); IsStoredProp 함수는 저장 한정자를 사용하여 속성을 저장해야 하는지 여부를 결정합니다. 속성을 저장하려면 WriteProperty를 호출하고 일련의 RTTI를 통해 WriteProperty를 구현합니다. 기능. 게시된 속성을 저장한 후 게시되지 않은 속성을 저장해야 합니다. Instance.DefineProperties(Self); DefineProperties의 구현은 이를 통해 저장됩니다. . 좋아요, 지금까지는 여전히 질문이 있습니다. 루트 구성 요소가 소유한 하위 구성 요소는 어떻게 저장됩니까? WriteData 메서드를 살펴보겠습니다(이 메서드는 앞에서 언급함): 절차 TWriter.WriteData(Instance: TComponent);...begin... IgnoreChildren이 아닌 경우 if (FAncestor <> nil) 및 (FAncestor is TComponent)를 시도합니다. if (FAncestor는 TComponent) 및 (csInline in TComponent(FAncestor).ComponentState) then FRootAncestor :=로 시작합니다. TComponent(FAncestor); TList.Create; TComponent(AddAncestor, FRootAncestor); if Instance.ComponentState then FRoot := Instance.GetChildren(WriteComponent, FRoot); 무료; 종료; IgnoreChildren 특성을 사용하면 Writer 개체가 구성 요소가 소유한 하위 구성 요소를 저장하지 않고 구성 요소를 저장할 수 있습니다. IgnoreChildren 속성이 True인 경우 Writer 개체는 자신이 소유한 하위 구성 요소를 저장하지 않고 구성 요소를 저장합니다. 그렇지 않으면 하위 구성 요소를 저장해야 합니다. Instance.GetChildren(WriteComponent, FRoot); 이는 하위 구성 요소를 작성할 때 가장 중요한 문장입니다. WriteComponent 메서드를 콜백 함수로 사용하며 루트 구성 요소 FRoot에 하위 구성 요소가 있는 경우 트리의 깊이 우선 순회 원칙을 따릅니다. , WriteComponent를 사용하여 하위 구성 요소를 저장합니다. 이런 식으로 DFM 파일에서 볼 수 있는 것은 트리와 같은 구성 요소 구조입니다.