Delphi: overridden method not called for objects created using RTTI

I am trying to clone objects using RTTI in D2010. Here is my attempt:

uses SysUtils, TypInfo, rtti; type TPerson = class(TObject) public Name: string; destructor Destroy(); Override; end; destructor TPerson.Destroy; begin WriteLn('A TPerson was freed.'); inherited; end; procedure CloneInstance(SourceInstance: TObject; DestinationInstance: TObject; Context: TRttiContext); Overload; var rSourceType: TRttiType; rDestinationType: TRttiType; rField: TRttiField; rSourceValue: TValue; Destination: TObject; rMethod: TRttiMethod; begin rSourceType := Context.GetType(SourceInstance.ClassInfo); if (DestinationInstance = nil) then begin rMethod := rSourceType.GetMethod('Create'); DestinationInstance := rMethod.Invoke(rSourceType.AsInstance.MetaclassType, []).AsObject; end; for rField in rSourceType.GetFields do begin if (rField.FieldType.TypeKind = tkClass) then begin // TODO: Recursive clone end else begin // Non-class values are copied (NOTE: will cause problems with records etc.) rField.SetValue(DestinationInstance, rField.GetValue(SourceInstance)); end; end; end; procedure CloneInstance(SourceInstance: TObject; DestinationInstance: TObject); Overload; var rContext: TRttiContext; begin rContext := TRttiContext.Create(); CloneInstance(SourceInstance, DestinationInstance, rContext); rContext.Free(); end; var Original: TPerson; Clone: TPerson; begin ReportMemoryLeaksOnShutdown := true; Original := TPerson.Create(); CloneInstance(Original, Clone); Clone.Free(); Original.Free(); ReadLn; end. 

A little disappointing, I do not see more than one case of "A TPerson was released." exit (which is confirmed by going through the program) - only the original is destroyed using an overridden destructor.

Can someone help me with calling an overridden destructor? (And perhaps explain why this is not caused in the first place.) Thank you!

+4
source share
2 answers

A couple of problems with your code.

You do not initialize the Clone variable to zero. Which on my machine led to access violations in the top CloneInstance method, since no clone was created because the value passed was not zero.

You do not have a DestinationInstance parameter declared as var. This means that the instance in the top CloneInstance method does not return to the caller. Adding the var parameter to the parameter solves the problem. You need to use TObject(Clone) in the CloneInstance call from the main program method, or Delphi will complain about "there is no overloaded method that can be called with these parameters." This is because var parameters require that their exact declared type be passed to them.

I changed your code to:

 uses SysUtils, TypInfo, rtti; type TPerson = class(TObject) public Name: string; constructor Create; destructor Destroy(); Override; end; constructor TPerson.Create; begin WriteLn('A TPerson was created'); end; destructor TPerson.Destroy; begin WriteLn('A TPerson was freed.'); inherited; end; procedure CloneInstance(SourceInstance: TObject; var DestinationInstance: TObject; Context: TRttiContext); Overload; var rSourceType: TRttiType; rDestinationType: TRttiType; rField: TRttiField; rSourceValue: TValue; Destination: TObject; rMethod: TRttiMethod; begin rSourceType := Context.GetType(SourceInstance.ClassInfo); if (DestinationInstance = nil) then begin rMethod := rSourceType.GetMethod('Create'); DestinationInstance := rMethod.Invoke(rSourceType.AsInstance.MetaclassType, []).AsObject; end; for rField in rSourceType.GetFields do begin if (rField.FieldType.TypeKind = tkClass) then begin // TODO: Recursive clone end else begin // Non-class values are copied (NOTE: will cause problems with records etc.) rField.SetValue(DestinationInstance, rField.GetValue(SourceInstance)); end; end; end; procedure CloneInstance(SourceInstance: TObject; var DestinationInstance: TObject); Overload; var rContext: TRttiContext; begin rContext := TRttiContext.Create(); CloneInstance(SourceInstance, DestinationInstance, rContext); rContext.Free(); end; var Original: TPerson; Clone: TPerson; begin Clone := nil; ReportMemoryLeaksOnShutdown := true; Original := TPerson.Create(); Original.Name := 'Marjan'; CloneInstance(Original, TObject(Clone)); Original.Name := 'Original'; WriteLn('Original name: ', Original.Name); WriteLn('Clone name: ', Clone.Name); Clone.Free(); Original.Free(); ReadLn; end. 

I added a constructor to see both the instances being created and a couple of lines to check the names after cloning. Output:

 A TPerson was created A TPerson was created Original name: Original Clone name: Marjan A TPerson was freed. A TPerson was freed. 
+5
source

An approximate solution (for the constructor, but basically also applicable in this case) is located in

How to create a Delphi object from a class reference and ensure constructor execution? in this answer

However, he must know the type of destination ... which may not be an option

0
source

Source: https://habr.com/ru/post/1388266/


All Articles