Modern programs should use TStream
class and its many descendants to do input / output. It has many useful descendants, like TFileStream
, TMemoryStream
, TStringStream
.
{$mode objfpc}{$H+}{$J-}
uses
SysUtils, Classes;
var
S: TStream;
InputInt, OutputInt: Integer;
begin
InputInt := 666;
S := TFileStream.Create('my_binary_file.data', fmCreate);
try
S.WriteBuffer(InputInt, SizeOf(InputInt));
finally
FreeAndNil(S);
end;
S := TFileStream.Create('my_binary_file.data', fmOpenRead);
try
S.ReadBuffer(OutputInt, SizeOf(OutputInt));
finally
FreeAndNil(S);
end;
WriteLn('Read from file got integer: ', OutputInt);
end.
In the Castle Game Engine: You should use the Download
function to create a stream that obtains data from any URL. Regular files, HTTP and HTTPS resources, Android assets and more are supported this way. Moreover, to open the resource inside your game data (in the data
subdirectory) use the special castle-data:/xxx
URL. Examples:
EnableNetwork := true;
S := Download('https://castle-engine.io/latest.zip');
S := Download('file:///home/michalis/my_binary_file.data');
S := Download('castle-data:/gui/my_image.png');
To read text files, we advise using the TTextReader
class. It provides a line-oriented API, and wraps a TStream
inside. The TTextReader
constructor can take a ready URL, or you can pass there your custom TStream
source.
Text := TTextReader.Create('castle-data:/my_data.txt');
try
while not Text.Eof do
WriteLnLog('NextLine', Text.ReadLn);
finally
FreeAndNil(Text);
end;
7.2. Containers (lists, dictionaries) using generics
The language and run-time library offer various flexible containers. There are a number of non-generic classes (like TList
and TObjectList
from the Contnrs
unit), there are also dynamic arrays (array of TMyType
). But to get the most flexibility and type-safety, I advise using generic containers for most of your needs.
The generic containers give you a lot of helpful methods to add, remove, iterate, search, sort… The compiler also knows (and checks) that the container holds only items of the appropriate type.
There are three libraries providing generics containers in FPC now:
We advise using the Generics.Collections
unit. The generic containers it implements are
-
packed with useful features,
-
very efficient (in particular important for accessing dictionaries by keys),
-
compatible between FPC and Delphi,
-
the naming is consistent with other parts of the standard library (like the non-generic containers from the Contnrs
unit).
In the Castle Game Engine: We use the Generics.Collections
intensively throughout the engine, and advise you to use Generics.Collections
in your applications too!
Most important classes from the Generics.Collections
unit are:
- TList
-
A generic list of types.
- TObjectList
-
A generic list of object instances. It can "own" children, which means that it will free them automatically.
- TDictionary
-
A generic dictionary.
- TObjectDictionary
-
A generic dictionary, that can "own" the keys and/or values.
Here’s how to you use a simple generic TObjectList
:
{$mode objfpc}{$H+}{$J-}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleList = specialize TObjectList<TApple>;
var
A: TApple;
Apples: TAppleList;
begin
Apples := TAppleList.Create(true);
try
A := TApple.Create;
A.Name := 'my apple';
Apples.Add(A);
A := TApple.Create;
A.Name := 'another apple';
Apples.Add(A);
Writeln('Count: ', Apples.Count);
Writeln(Apples[0].Name);
Writeln(Apples[1].Name);
finally FreeAndNil(Apples) end;
end.
Note that some operations require comparing two items, like sorting and searching (e.g. by Sort
and IndexOf
methods). The Generics.Collections
containers use for this a comparer. The default comparer is reasonable for all types, even for records (in which case it compares memory contents, which is a reasonable default at least for searching using IndexOf
).
When sorting the list you can provide a custom comparer as a parameter. The comparer is a class implementing the IComparer
interface. In practice, you usually define the appropriate callback, and use TComparer<T>.Construct
method to wrap this callback into an IComparer
instance. An example of doing this is below:
{$mode objfpc}{$H+}{$J-}
{$ifdef VER3_0} {$define GENERICS_CONSTREF} {$endif}
{$ifdef VER3_2_0} {$define GENERICS_CONSTREF} {$endif}
{$ifdef VER3_2_2} {$define GENERICS_CONSTREF} {$endif}
uses SysUtils, Generics.Defaults, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleList = specialize TObjectList<TApple>;
function CompareApples(
{$ifdef GENERICS_CONSTREF}constref{$else}const{$endif}
Left, Right: TApple): Integer;
begin
Result := AnsiCompareStr(Left.Name, Right.Name);
end;
type
TAppleComparer = specialize TComparer<TApple>;
var
A: TApple;
L: TAppleList;
begin
L := TAppleList.Create(true);
try
A := TApple.Create;
A.Name := '11';
L.Add(A);
A := TApple.Create;
A.Name := '33';
L.Add(A);
A := TApple.Create;
A.Name := '22';
L.Add(A);
L.Sort(TAppleComparer.Construct(@CompareApples));
Writeln('Count: ', L.Count);
Writeln(L[0].Name);
Writeln(L[1].Name);
Writeln(L[2].Name);
finally FreeAndNil(L) end;
end.
The TDictionary
class implements a dictionary, also known as a map (key → value), also known as an associative array. Its API is a bit similar to the C# TDictionary
class. It has useful iterators for keys, values, and pairs of key→value.
An example code using a dictionary:
{$mode objfpc}{$H+}{$J-}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleDictionary = specialize TDictionary<string, TApple>;
var
Apples: TAppleDictionary;
A, FoundA: TApple;
ApplePair: TAppleDictionary.TDictionaryPair;
AppleKey: string;
begin
Apples := TAppleDictionary.Create;
try
A := TApple.Create;
A.Name := 'my apple';
Apples.AddOrSetValue('apple key 1', A);
if Apples.TryGetValue('apple key 1', FoundA) then
Writeln('Found apple under key "apple key 1" with name: ' +
FoundA.Name);
for AppleKey in Apples.Keys do
Writeln('Found apple key: ' + AppleKey);
for A in Apples.Values do
Writeln('Found apple value: ' + A.Name);
for ApplePair in Apples do
Writeln('Found apple key->value: ' +
ApplePair.Key + '->' + ApplePair.Value.Name);
Apples.Remove('apple key 1');
A.Free;
finally FreeAndNil(Apples) end;
end.
The TObjectDictionary
can additionally own the dictionary keys and/or values, which means that they will be automatically freed. Be careful to only own keys and/or values if they are object instances. If you set to "owned" some other type, like an Integer
(for example, if your keys are Integer
, and you include doOwnsKeys
), you will get a nasty crash when the code executes.
An example code using the TObjectDictionary
is below. Compile this example with memory leak detection, like fpc -gl -gh generics_object_dictionary.lpr
, to see that everything is freed when program exits.
{$mode objfpc}{$H+}{$J-}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleDictionary = specialize TObjectDictionary<string, TApple>;
var
Apples: TAppleDictionary;
A: TApple;
ApplePair: TAppleDictionary.TDictionaryPair;
begin
Apples := TAppleDictionary.Create([doOwnsValues]);
try
A := TApple.Create;
A.Name := 'my apple';
Apples.AddOrSetValue('apple key 1', A);
for ApplePair in Apples do
Writeln('Found apple key->value: ' +
ApplePair.Key + '->' + ApplePair.Value.Name);
Apples.Remove('apple key 1');
finally FreeAndNil(Apples) end;
end.
If you prefer using the FGL
unit instead of Generics.Collections
, the most important classes from the FGL
unit are:
- TFPGList
-
A generic list of types.
- TFPGObjectList
-
A generic list of object instances. It can "own" children.
- TFPGMap
-
A generic dictionary.
In FGL
unit, the TFPGList
can be only used for types for which the equality operator (=) is defined. For TFPGMap
the "greater than" (>) and "less than" (<) operators must be defined for the key type. If you want to use these lists with types that don’t have built-in comparison operators (e.g. with records), you have to overload their operators as shown in the Operator overloading.
In the Castle Game Engine we include a unit CastleGenericLists
that adds TGenericStructList
and TGenericStructMap
classes. They are similar to TFPGList
and TFPGMap
, but they do not require a definition of the comparison operators for the appropriate type (instead, they compare memory contents, which is often appropriate for records or method pointers). But the CastleGenericLists
unit is deprecated since the engine version 6.3, as we advise using Generics.Collections
instead.
If you want to know more about the generics, see Generics.
7.3. Cloning: TPersistent.Assign
Copying the class instances by a simple assignment operator copies the reference.
var
X, Y: TMyObject;
begin
X := TMyObject.Create;
Y := X;
Y.MyField := 123;
FreeAndNil(X);
end;
To copy the class instance contents, the standard approach is to derive your class from TPersistent
, and override its Assign
method. Once it’s implemented properly in TMyObject
, you use it like this:
var
X, Y: TMyObject;
begin
X := TMyObject.Create;
Y := TMyObject.Create;
Y.Assign(X);
Y.MyField := 123;
FreeAndNil(X);
FreeAndNil(Y);
end;
To make it work, you need to implement the Assign
method to actually copy the fields you want. You should carefully implement the Assign
method, to copy from a class that may be a descendant of the current class.
{$mode objfpc}{$H+}{$J-}
uses
SysUtils, Classes;
type
TMyClass = class(TPersistent)
public
MyInt: Integer;
procedure Assign(Source: TPersistent); override;
end;
TMyClassDescendant = class(TMyClass)
public
MyString: string;
procedure Assign(Source: TPersistent); override;
end;
procedure TMyClass.Assign(Source: TPersistent);
var
SourceMyClass: TMyClass;
begin
if Source is TMyClass then
begin
SourceMyClass := TMyClass(Source);
MyInt := SourceMyClass.MyInt;
end else
inherited Assign(Source);
end;
procedure TMyClassDescendant.Assign(Source: TPersistent);
var
SourceMyClassDescendant: TMyClassDescendant;
begin
if Source is TMyClassDescendant then
begin
SourceMyClassDescendant := TMyClassDescendant(Source);
MyString := SourceMyClassDescendant.MyString;
end;
inherited Assign(Source);
end;
var
C1, C2: TMyClass;
CD1, CD2: TMyClassDescendant;
begin
C1 := TMyClass.Create;
C2 := TMyClass.Create;
try
C1.MyInt := 666;
C2.Assign(C1);
WriteLn('C2 state: ', C2.MyInt);
finally
FreeAndNil(C1);
FreeAndNil(C2);
end;
CD1 := TMyClassDescendant.Create;
CD2 := TMyClassDescendant.Create;
try
CD1.MyInt := 44;
CD1.MyString := 'blah';
CD2.Assign(CD1);
WriteLn('CD2 state: ', CD2.MyInt, ' ', CD2.MyString);
finally
FreeAndNil(CD1);
FreeAndNil(CD2);
end;
end.
Sometimes it’s more comfortable to alternatively override the AssignTo
method in the source class, instead of overriding the Assign
method in the destination class.
Be careful when you call inherited
in the overridden Assign
implementation. There are two situations:
- Your class is a direct descendant of the
TPersistent
class. (Or, it’s not a direct descendant of TPersistent
, but no ancestor has overridden the Assign
method.)
-
In this case, your class should use the inherited
keyword (to call the TPersistent.Assign
) only if you cannot handle the assignment in your code.
- Your class descends from some class that has already overridden the
Assign
method.
-
In this case, your class should always use the inherited
keyword (to call the ancestor Assign
). In general, calling inherited
in overridden methods is usually a good idea.
To understand the reason behind the above rule (when you should call, and when you should not call inherited
from the Assign
implementation), and how it relates to the AssignTo
method, let’s look at the TPersistent.Assign
and TPersistent.AssignTo
implementations:
procedure TPersistent.Assign(Source: TPersistent);
begin
if Source <> nil then
Source.AssignTo(Self)
else
raise EConvertError...
end;
procedure TPersistent.AssignTo(Destination: TPersistent);
begin
raise EConvertError...
end;
Note
|
This is not the exact implementation of TPersistent . I copied the FPC standard library code, but then I simplified it to hide unimportant details about the exception message.
|
The conclusions you can get from the above are:
-
If neither Assign
nor AssignTo
are overridden, then calling them will result in an exception.
-
Also, note that there is no code in TPersistent
implementation that automatically copies all the fields (or all the published fields) of the classes. That’s why you need to do that yourself, by overriding Assign
in all the classes. You can use RTTI (runtime type information) for that, but for simple cases you will probably just list the fields to be copied manually.
When you have a class like TApple
, your TApple.Assign
implementation usually deals with copying fields that are specific to the TApple
class (not to the TApple
ancestor, like TFruit
). So, the TApple.Assign
implementation usually checks whether Source is TApple
at the beginning, before copying apple-related fields. Then, it calls inherited
to allow TFruit
to handle the rest of the fields.
Assuming that you implemented TFruit.Assign
and TApple.Assign
following the standard pattern (as shown in the example above), the effect is like this:
-
If you pass TApple
instance to TApple.Assign
, it will work and copy all the fields.
-
If you pass TOrange
instance to TApple.Assign
, it will work and only copy the common fields shared by both TOrange
and TApple
. In other words, the fields defined at TFruit
.
-
If you pass TWerewolf
instance to TApple.Assign
, it will raise an exception (because TApple.Assign
will call TFruit.Assign
which will call TPersistent.Assign
which raises an exception).
Note
|
Remember that when descending from TPersistent , the default visibility specifier is published , to allow streaming of TPersistent descendants. Not all field and property types are allowed in the published section. If you get errors related to it, and you don’t care about streaming, just change the visibility to public . See the Visibility specifiers.
|