Search code examples
delphidelphi-7

How to initialize var Record parameter


I have the record type:

type
  TIPInfo = record
    IP,
    HostName,
    City,
    Region,
    Country,
    Loc,
    Org: WideString
  end;

Function to return the record data:

function GetPublicIPInfo(var IPInfo: TIPInfo): Boolean;
begin
  // initialize
  FillChar(IPInfo, SizeOf(TIPInfo), 0);

  // populate data
  IPInfo.IP := GetVallue('ip');
  IPInfo.HostName := GetVallue('hostname');
  IPInfo.City := GetVallue('city');
  // etc...

  Result := IsOk;   
end;

The caller:

var
  IPInfo: TIPInfo;

if GetPublicIPInfo(IPInfo) then... // use data

Is it the correct way to initialize the var TIPInfo by calling FillChar or should I set every field to empty string? Should the caller do that?

Also, would it be better to use an out parameter in the case (since the function is not reading the data)?


Solution

  • Using just FillChar is wrong here. If any of the WideString members are not empty, then you will leak them this way. Instead I suggest the following:

    Finalize(IPInfo);
    FillChar(IPInfo, SizeOf(TIPInfo), 0);
    

    Or another way is to define a default record as a typed constant:

    const
      DefaultIPInfo: TIPInfo = ();
    

    Then you can use simple assignment:

    IPInfo := DefaultIPInfo;
    

    In modern versions of Delphi you can instead use this much more readable code:

    IPInfo := Default(TIPInfo);
    

    For more on this particular subject, refer to these topics:

    Note that the leak in your code is hard to find because WideString variables are implemented as COM BSTR objects, and allocated on the COM heap. Therefore, if you use memory leak detection for the Delphi memory manager the leak will not be detected because it is leaked from a different heap.

    In your case, because your record is a managed type, and contains only managed types, then you could use an out parameter to good effect. For managed types, out parameters mean that the compiler will generate code, at the call site, to default initialize the record before passing it in.

    Consider the following program:

    {$APPTYPE CONSOLE}
    
    type
      TRec = record
        Value: WideString;
      end;
    
    procedure Foo1(var rec: TRec);
    begin
    end;
    
    procedure Foo2(out rec: TRec);
    begin
    end;
    
    procedure Main;
    var
      rec: TRec;
    begin
      rec.Value := 'Foo';
      Foo1(rec);
      Writeln(rec.Value);
      Foo2(rec);
      Writeln(rec.Value);
    end;
    
    begin
      Main;
    end.
    

    The output is:

    Foo
    
    

    If your record contains a mix of managed and unmanaged types, then the situation is not so good.

    {$APPTYPE CONSOLE}
    
    type
      TRec = record
        Value1: WideString;
        Value2: Integer;
      end;
    
    procedure Foo1(var rec: TRec);
    begin
    end;
    
    procedure Foo2(out rec: TRec);
    begin
    end;
    
    procedure Main;
    var
      rec: TRec;
    begin
      rec.Value1 := 'Foo';
      rec.Value2 := 42;
      Foo1(rec);
      Writeln(rec.Value1);
      Writeln(rec.Value2);
      Foo2(rec);
      Writeln(rec.Value1);
      Writeln(rec.Value2);
    end;
    
    begin
      Main;
    end.
    

    The output is:

    Foo
    42
    
    42
    

    Only the managed members are default initialized for out parameters. So your best bet is to default initializing the variable yourself, even if it is passed as an out parameter.

    More on out parameters can be found here: What's the difference between "var" and "out" parameters?