#1 2014-11-14 13:15:04

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Automatic TSQLRecord memory handling

Working with objects is pretty powerful, but requires to handle manually the created instances life time, via try .. finally blocks. Most of the time, the TSQLRecord life time would be very short: we allocate one instance on a local variable, then release it when it goes out of scope.

If we take again the TSQLBaby sample, we may write:

function NewMaleBaby(Client: TSQLRest; const Name,Address: RawUTF8): TID;
var Baby: TSQLBaby;   // store a record
begin
  Baby := TSQLBaby.Create;
  try
    Baby.Name := Name;
    Baby.Address := Address;
    Baby.BirthDate := Date;
    Baby.Sex := sMale;
    result := Client.Add(Baby);
  finally
    Baby.Free;
  end;
end;     
To ease this pretty usual pattern, the framework offers some kind of automatic memory management at TSQLRecord level:
function NewMaleBaby(Client: TSQLRest; const Name,Address: RawUTF8): TID;
var Baby: TSQLBaby;   // store a record
begin
  TSQLBaby.AutoFree(Baby);  // no try..finally needed!
  Baby.Name := Name;
  Baby.Address := Address;
  Baby.BirthDate := Date;
  Baby.Sex := sMale;
  result := Client.Add(Baby);
end; // local Baby instance will be released here

See http://synopse.info/files/html/Synopse% … l#TITL_130
and http://blog.synopse.info/post/2014/11/1 … y-handling

Offline

#2 2014-11-14 16:56:34

RalfS
Member
Registered: 2011-07-11
Posts: 57

Re: Automatic TSQLRecord memory handling

I do this in similar form since years, but be careful: It does not work under FPC. FPCs IUnknown is working differently from Delphi' one. For details, please look at FPC bug tracker.
http://bugs.freepascal.org/view.php?id=26602

Last edited by RalfS (2014-11-14 17:09:36)

Offline

#3 2014-11-14 20:13:32

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Automatic TSQLRecord memory handling

@RalfS

Thanks for the disappointing information!
I have included the warning to the documentation: a local IAutoFree variable should be defined.
See http://synopse.info/files/html/Synopse% … l#TITL_130

Offline

#4 2018-10-18 11:57:13

malom
Member
Registered: 2017-01-12
Posts: 4

Re: Automatic TSQLRecord memory handling

I played a lot with Your nice IAutoFree idea, and finally got to a different implementation, currently for test purposes, working only with a single object instance.
I really can't decide wether this is cool or fool, so I woudld be appreciated on any comment.

// TAutoFreeData - useful for simple classes that do not have constructor and destructor ie. for emulating pascal record behavior with property access syntax.

type

  // Memory signature on the heap
  PAutoFreeRec = ^TAutoFreeRec;
  TAutoFreeRec = packed record
    AutoFree: packed record
      ClassType: TClass;
      RefCount: Integer;
      Unknown: {array[0..0] of} Pointer; // TODO: What is this? Actually initialized by TAutoFreeData.InitInstance().
    end;
    Instance: packed record
      ClassType: TClass;
      // Data: array[1..Instance.ClassType.InstanceSize - SizeOf(Instance.ClassType)] of Byte;
    end;
  end;

  // Implements the reference counted interface
  TAutoFreeData = class(TInterfacedObject)
  public
    destructor Destroy; override;
  end;

  // Just for accessing RefCount
  TAutoFreeInfo = class
  private
    function getAutoFree: TAutoFreeData;
  public
    property AutoFree: TAutoFreeData read getAutoFree;
  end;

destructor TAutoFreeData.Destroy;
begin
  //* Cleanup aClassType instance
  // MUST NOT call Instance.Destroy, since
  // it is not directly allocated on the heap, and destructor finally calls System._FreeMem()
  TObject(@PAutoFreeRec(Self)^.Instance).CleanupInstance;
  //* Destroy AutoFree instance and Free allocated memory
  inherited;
end;

function TAutoFreeInfo.getAutoFree: TAutoFreeData;
begin
  Result := TAutoFreeData(@PAutoFreeRec(Integer(Self) - SizeOf(PAutoFreeRec(nil)^.AutoFree))^.AutoFree);
end;

function AutoFreeData(var aLocalVar; aClassType: TClass): IInterface;
var
  Size: integer;
begin
  //* Allocate space on heap for TAutoFreeData and aClassType instance
  Size := TAutoFreeData.InstanceSize + aClassType.InstanceSize;
  GetMem(Pointer(aLocalVar), Size);
  //* Fill only Instance.Data with zeroes
  // FillChar(Pointer(aLocalVar)^, Size, 0);
  FillChar(PAutoFreeRec(Integer(aLocalVar) + SizeOf(TAutoFreeRec))^, Size - SizeOf(TAutoFreeRec), 0);
  //* Initialize AutoFreeData instance and return interface reference
  Result := TAutoFreeData(TAutoFreeData.InitInstance(TAutoFreeData(aLocalVar)));
  //* Initialize aClass instance
  //* Not necessary to call aClassType.InitInstance on constructorless simple classes
  // aClassType.InitInstance(TObject(@PAutoFreeRec(aLocalVar)^.Instance));
  PAutoFreeRec(aLocalVar)^.Instance.ClassType := aClassType;
  //* Set aLocalVar
  Pointer(aLocalVar) := @PAutoFreeRec(aLocalVar)^.Instance;
end;

type
  // Not mandatory to inherit from TAutoFreeInfo. It is just for test.
  TTestRec28C = class(TAutoFreeInfo)
  private
    fID: Integer;
    fName: string;
  published
    property ID: Integer read fID write fID;
    property Name: string read fName write fName;
  end;

  // Inheritance and virtual functions will work
  TTestRec28VC = class(TTestRec28C)
    function getCalculated: string; virtual;
  published
    property Calculated: string read getCalculated;
  end;

function TTestRec28VC.getCalculated: string;
begin
  Result := Format('ID: %d, Name: %s', [ID, Name]);
end;

procedure Test28;
var
  d1, d2: IInterface;
  c: TTestRec28C;

  sl: TStringList;

  procedure Log(const Fmt: string; const Params: array of const);
  begin
    sl.Add(Format(Fmt, Params));
  end;

begin
  sl := TStringList.Create;

  d1 := AutoFreeData(c, TTestRec28VC);
  Log('c.ClassName: %s', [c.ClassName]);
  Log('c.AutoFree.ClassName: %s', [c.AutoFree.ClassName]);
  Log('c.AutoFree.RefCount: %d', [c.AutoFree.RefCount]);
  c.ID := 1;
  c.Name := 'First';
  Log('c.ID: %d', [c.ID]);
  Log('c.Name: %s', [c.Name]);
  Log('c.Calculated: %s', [(c as TTestRec28VC).Calculated]);
  d2 := d1;
  Log('c.AutoFree.RefCount: %d', [c.AutoFree.RefCount]);

  sl.SaveToFile(IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'Test28.txt');
  sl.Free;
end;

Offline

#5 2018-10-18 14:11:50

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Automatic TSQLRecord memory handling

Please don't post huge pieces of code directly in the form - use a gist or something similar.
See the forum rules: https://synopse.info/forum/misc.php?action=rules

About your proposal, I don't see much benefit about using such a record as wrapper.
The main bottleneck is heap allocation.
The performance benefit won't be noticeable, unless the method on which it is applied will be very quick - and in this case, you won't use an auto-free, but manual try...finally for sure.
I am afraid it won't be very portable (e.g. on FPC).

Offline

Board footer

Powered by FluxBB