I was always thinking about interfaces as a way to give different unrelated classes a common functionality. But the property of interface - "free an object when RefCOunt drops to zero" does not allow me to work as I want to.
For example: lets assume that I have two different classes: TMyObject and TMyDifferentObject. They both support this interface:
const
IID_MyInterface: TGUID = '{4D91C27F-510D-4673-8773-5D0569DFD168}';
type
IMyInterface = Interface(IInterface)
['{4D91C27F-510D-4673-8773-5D0569DFD168}']
function GetID : Integer;
end;
type
TMyObject = class(TInterfacedObject, IMyInterface)
function GetID: Integer;
end;
function TMyObject.GetID: Integer;
begin
Result := 1;
end;
type
TMyDifferentObject = class(TInterfacedObject, IMyInterface)
function GetID: Integer;
end;
function TMyDifferentObject.GetID: Integer;
begin
Result := 2;
end;
Now, I would like to create instances of this classes in my program, and then pass those instances to this method:
procedure ShowObjectID(AObject: TObject);
var
MyInterface: IMyInterface;
begin
if Supports(AObject, IID_MyInterface, MyInterface) then
begin
ShowMessage(IntToStr(MyInterface.GetID));
end;
end; //Interface goes out of scope and AObject is freed but I still want to work with that object!
This is an example. In general I want to pass instance of object to some procedure and check if this object supports an interface, if yes I want to execute method of this interface. But I don't want to finish work with that object when interface goes out of scope. How to do this?
Regards.
Your problem probably stems from the fact that you create your objects using an object reference:
var
MyObject: TObject;
begin
MyObject := TMyObject.Create;
ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
ShowObjectID(MyObject);
ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;
Doing it like this means the RefCount after creation is zero. Either assign your object to an interface reference as well for as long as you need it,
var
MyObject: TMyObject;
MyIntf: IMyInterface;
begin
MyObject := TMyObject.Create;
MyIntf := MyObject;
ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
ShowObjectID(MyObject);
ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
MyIntf := nil;
ShowMessage('After nilling the interface MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;
or disable refcounting as David suggested in the comments. Which essentially means declaring your own "TInterfacedObject" and implementing the three IInterface methods:
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
The essence is to return -1 for both _AddRef and _Release. As David said: have a look at how TComponent does it. And just take what it is doing when FVCLComObject is nil.
One approach to solve your problem is to change your code so that you only ever refer to the object through an interface reference. In other words instead of
var
obj: TMyObject;
...
obj := TMyObject.Create;
try
obj.DoStuff;
//etc. etc.
finally
obj.Free;
end;
you write
var
obj: IMyObject;//NOTE: interface variable
...
obj := TMyObject.Create;
obj.DoStuff;
//etc. etc.
obj := nil;//or let it go out of scope and release that way
This can be inconvenient, so instead it can be more convenient to disable automatic lifetime management. You need to do this for your implementing object:
type
TInterfacedObjectWithoutLifetimeManagement = class(TObject, IInterface)
private
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
function TInterfacedObjectWithoutLifetimeManagement.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TInterfacedObjectWithoutLifetimeManagement._AddRef: Integer;
begin
Result := -1;
end;
function TInterfacedObjectWithoutLifetimeManagement._Release: Integer;
begin
Result := -1;
end;
You can then derive your classes from this class.
There is one very major caveat with this approach. Suppose that you hold in variables (local, global, class member) any interfaces that are implemented by a class derived from TInterfacedObjectWithoutLifetimeManagement. All such interface variables must be finalised before you call Free on the implementing object.
If you do not follow this rule you will find that when those interface variables go out of scope, the compiler still emits code to call _Release and it's an error to call a method on an object after it has been destroyed. This is a particularly nasty type of error because it commonly will not manifest itself with a runtime failure until your code runs on your most important client's machine! In other words such errors can be of intermittent nature.
Another option nobody mentioned so far is to explicitly call _AddRef on the object instance to keep it alive as long as you need it, then call _Release.
Related
I have two interfaces, ISomeInterfaceRO (read only) and ISomeInterface.
ISomeInterfaceRO = interface(IInterface) ['{B28A9FB0-841F-423D-89AF-E092FE04433F}']
function GetTest: Integer;
property Test : integer read GetTest;
end;
ISomeInterface = interface(ISomeInterfaceRO) ['{C7148E40-568B-4496-B923-89BB891A7310}']
procedure SetTest(const aValue: Integer);
property Test : integer read GetTest write SetTest;
end;
TSomeClass = class(TInterfacedObject, ISomeInterfaceRO, ISomeInterface)
private
fTest: integer;
protected
function GetTest: integer;
procedure SetTest(const aValue: integer);
public
property Test: integer read GetTest write SetTest;
end;
function TSomeClass.GetTest: integer;
begin
Result := fTest;
end;
procedure TSomeClass.SetTest(const aValue: integer);
begin
fTest := aValue;
end;
Then, i use read only interface except one place, when i create TSomeClass instance as ISomeInterface and fill it. example:
Function GetSome: ISomeInterfaceRO;
var
SomeInterface: ISomeInterface;
begin
SomeInterface := TSomeClass.Create;
SomeInterface.Test := 10;
result := SomeInterface as ISomeInterfaceRO;
end;
My question is: that "result := SomeInterface as ISomeInterfaceRO;" is a safe and recommended construction? Or is a another way to do this?
I debugged that code, and compiler properly decreased reference count to ISomeInterface and increased to ISomeInterfaceRO when i use "as".
Result := SomeInterface as ISomeInterfaceRO;
is safe but not necessary at all because ISomeInterface inherits from ISomeInterfaceRO and thus SomeInterface is assignment compatible to Result. That means you can just write
Result := SomeInterface;
I however would put a constructor on TSomeClass that takes the value so you can directly write:
Result := TSomeClass.Create(10);
I would like to copy generic object but its type can only be obtained by the "class of" construct at runtime as the source object type may be different (TItem or TSpecificItem etc.):
type
TItem = class
//...
procedure Assign(Source: TItem);virtual; abstract; //edit
end;
TSpecificItem = class(TItem)
//...
end;
TEvenMoreSpecificItem = class(TSpecificItem)
//...
end;
TItemClass = class of TItem;
TItemContainer = class
FItems: TObjectList<TItem>; //edit
procedure Assign(Source: TObject); //edit
function GetItem(Index: Integer): TItem; inline; //edit
procedure SetItem(Index: Integer; Item: TItem); inline; //edit
function Count: Integer; //edit;
function ItemClass: TItemClass; virtual; abstract;
property Items[Index: Integer]: TItem read GetItem write SetItem; //edit
end;
TItemContainer<T: TItem> = class(TItemContainer)
//...
function GetItem(Index: Integer): T; inline; //edit
procedure SetItem(Index: Integer; Item: T); inline; //edit
function ItemClass: TItemClass; override;
property Items[Index: Integer]: T read GetItem write SetItem; default; //edit
end;
//start of edit
function TItemContainer.Count: Integer;
begin
Result := FItems.Count;
end;
function TItemContainer.GetItem(Index: Integer): TItem;
begin
Result := FItems[Index];
end;
procedure TItemContainer.SetItem(Index: Integer; Item: TItem);
begin
FItems[Index].Assign(Item);
end;
procedure TItemContainer.Assign(Source: TObject);
var
I: Integer;
Item: TItem;
Cls: TClass;
begin
if Source is TItemContainer then
begin
FItems.Clear;
for I := 0 to TItemContainer(Source).Count - 1 do
begin
Item := TItemContainer(Source).Items[I];
Cls := Item.ClassType;
Item := TItemClass(Cls).Create;
Item.Assign(TItemContainer(Source).Items[I]);
FItems.Add(Item);
end;
end;
end;
function TItemContainer<T>.GetItem(Index: Integer): T;
begin
Result := T(inherited GetItem(Index));
end;
procedure TItemContainer<T>.SetItem(Index: Integer; Item: T);
begin
inherited SetItem(Index, Item);
end;
//end of edit
function TItemContainer<T>.ItemClass: TItemClass;
begin
Result := TItemClass(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType);
end;
function CopyGenericObject(Source: TItemContainer): TItemContainer;
var
Cls: TItemClass;
begin
Cls := Source.ItemClass;
Result := TItemContainer<Cls>.Create; // compiler reports error "incompatible types"
Result.Assign(Source);
end;
// edit:
procedure DoCopy;
var
Source: TItemContainer<TEvenMoreSpecificItem>;
Dest: TItemContainer;
begin
Source := TItemContainer<TEvenMoreSpecificItem>.Create; // for example
//add some items to Source
Dest := CopyGenericObject(Source);
//use the result somewhere
end;
I must Use Delphi XE.
I've found
http://docwiki.embarcadero.com/RADStudio/XE6/en/Overview_of_Generics
Dynamic instantiation
Dynamic instantiation at run time is not supported.
Is it what I want to do?
If I understand well, what you are looking for is to implement a routine that will create an instance of a class of the same type as a given source. This can be done like this :
type
TItemContainerclass = class of TItemContainer;
function CopyGenericObject(Source: TItemContainer): TItemContainer;
begin
Result := TItemContainerclass(Source.ClassType).Create;
end;
Also, you can simplify the ItemClass routine to
function TItemContainer<T>.ItemClass: TItemClass;
begin
Result := T;
end;
Note that this will only create a new instance and not a copy of the source, but since your code doesn't show any attempt to copy the object and only create a new instance, I presumed this is your intended result.
Note : This works in Delphi 10, I don't have access to XE to test it.
The line
Cls := Source.ItemClass;
will create the TItemClass instance at run time only. For Generics, the compiler needs to know the type at compile time. Without knowing it, the compiler can not generate the binary code which implements your specific TItemContainer<Cls>. Or, said in other words, Cls must not be a variable, it has to be a specific class type, known at compile time.
So for example these will compile:
Result := TItemContainer<TSpecificItem>.Create;
or
Result := TItemContainer<TEvenMoreSpecificItem>.Create;
but not this
Result := TItemContainer</* type will be known later */>.Create;
because the compiler is not able to come back later and complete the binary application code based on the actual type of Cls.
You can make CopyGenericObject function as a method of your generic object instead of stand-alone function:
TItemContainer<T: TItem> = class(TItemContainer)
...
function Copy: TItemContainer<T>;
end;
In this case, it "knows" at compile-time, what class to create just because there are now several of them (one for each Instantiated type) after compiler did its work, each making copy of itself.
There is one more trick which may be useful in your case: how to copy various objects. For example, you have common class TAnimal and its descendants: TCat and TDog. You store them in TItemContainer, that's the whole point of inheritance that you can do it and treat them generally. Now, you want to implement creating a copy of this container and you don't know at compile time, which elements will be dogs and which will be cats. Standart method is to define abstract function Copy in TAnimal:
TAnimal = class
public
...
function Copy: TAnimal; virtual; abstract;
end;
and then implement it in each descendant, so then you can copy your TItemContainer like this:
function TItemContainer<T>.Copy: TItemContainer<T>;
var i: T;
begin
Result:=TItemContainer<T>.Create;
for i in Items do
//I don't know exact structure of your container,
//maybe that's more like
// for j:=0 to Count-1 do begin
// i:=Items[j];
//but I hope it's obvious what happens here
Result.Add(i.copy as T);
end;
So if you have container of cats, then i.copy will return TAnimal (but actually a cat) which will be cast to TCat at last. It works but a bit ugly.
In delphi I came up with better solution: make this copy a constructor, not a function:
TAnimal = class
public
...
constructor Copy(source: TAnimal); virtual;
end;
In that case copying your container is like this:
function TItemContainer<T>.Copy: TItemContainer<T>;
var i,j: T;
begin
Result:=TItemContainer<T>.Create;
for i in Items do
Result.Add(T.Copy(i));
end;
no extra casting which is good. What's more, you can for example derive your classes from TPersistent and implement Assign procedure everywhere you need (very useful thing) and then once and for all write a copy constructor:
TAnimal = class(TPersistent)
public
constructor Copy(source: TPersistent); //or maybe source: TAnimal
end;
//implementation
constructor TAnimal.Copy(source: TPersistent);
begin
Create;
Assign(source);
end;
The following does not compile, but is something like it possible?
IDefaultHelp = interface
['{6997FC42-7481-4CDA-940A-0351071266C7}']
function GetTemplate: TXMLDocument;
end;
TDefaultHelp = class(TInterfacedObject, INodeHelp)
class function GetTemplate: TXMLDocument; static; <<-- error
end;
I don't want to have to instantiate the implementing object.
Is there a way to implement the interface without having to Create an actual class?
I must admit that I don't really see the need to avoid instantiating an instance. Now, you cannot use static class methods to implement an interface. You can implement an interface by delegating to static class methods, if you so wish.
I don't want to have to instantiate the implementing object.
So, taking your question as a desire to implement interfaces without the need to instantiate objects, you can use a constant vtable, implemented in the fashion of the comparer interfaces from the Generics.Defaults unit.
For example:
unit Unit1;
interface
uses
Xml.XMLDoc;
type
IDefaultHelp = interface
['{6997FC42-7481-4CDA-940A-0351071266C7}']
function GetTemplate: IXMLDocument;
end;
function GetDefaultHelp: IDefaultHelp;
implementation
function NopAddref(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopRelease(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult;
stdcall;
begin
Result := E_NOINTERFACE;
end;
function GetTemplate(inst: Pointer): IXMLDocument;
begin
Result := TXMLDocument.Create(nil);
end;
const
DefaultHelp_Vtable: array[0..3] of Pointer =
(
#NopQueryInterface,
#NopAddref,
#NopRelease,
#GetTemplate
);
DefaultHelp_Instance: Pointer = #DefaultHelp_Vtable;
function GetDefaultHelp: IDefaultHelp;
begin
Result := IDefaultHelp(#DefaultHelp_Instance);
end;
end.
I need a base class like TInterfacedObject but without reference counting (so a kind of TNonRefCountedInterfacedObject).
This actually is the nth time I need such a class and somehow I always end up writing (read: copy and pasting) my own again and again. I cannot believe that there is no "official" base class I can use.
Is there a base class somewhere in the RTL implementing IInterface but without reference counting which I can derive my classes from?
In the unit Generics.Defaults there is a class TSingletonImplementation defined. Available in Delphi 2009 and above.
// A non-reference-counted IInterface implementation.
TSingletonImplementation = class(TObject, IInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
I did this. It can be used in place of TInterfacedObject with or without reference counting. It also has a name property - very useful when debugging.
// TArtInterfacedObject
// =============================================================================
// An object that supports interfaces, allowing naming and optional reference counting
type
TArtInterfacedObject = class( TInterfacedObject )
constructor Create( AReferenceCounted : boolean = True);
PRIVATE
FName : string;
FReferenceCounted : boolean;
PROTECTED
procedure SetName( const AName : string ); virtual;
PUBLIC
property Name : string
read FName
write SetName;
function QueryInterface(const AGUID : TGUID; out Obj): HResult; stdcall;
function SupportsInterface( const AGUID : TGUID ) : boolean;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
// =============================================================================
{ TArtInterfacedObject }
constructor TArtInterfacedObject.Create( AReferenceCounted : boolean = True);
begin
inherited Create;
FName := '';
FReferenceCounted := AReferenceCounted;
end;
function TArtInterfacedObject.QueryInterface(const AGUID: TGUID; out Obj): HResult;
const
E_NOINTERFACE = HResult($80004002);
begin
If FReferenceCounted then
Result := inherited QueryInterface( AGUID, Obj )
else
if GetInterface(AGUID, Obj) then Result := 0 else Result := E_NOINTERFACE;
end;
procedure TArtInterfacedObject.SetName(const AName: string);
begin
FName := AName;
end;
function TArtInterfacedObject.SupportsInterface(
const AGUID: TGUID): boolean;
var
P : TObject;
begin
Result := QueryInterface( AGUID, P ) = S_OK;
end;
function TArtInterfacedObject._AddRef: Integer;
begin
If FReferenceCounted then
Result := inherited _AddRef
else
Result := -1 // -1 indicates no reference counting is taking place
end;
function TArtInterfacedObject._Release: Integer;
begin
If FReferenceCounted then
Result := inherited _Release
else
Result := -1 // -1 indicates no reference counting is taking place
end;
// =============================================================================
You might consider TInterfacedPersistent. If you don't override GetOwner it does no ref-counting.
I don't know of any out-of-the-box base class, so I wrote my own (like you). Just put it in a common utils unit and you are done.
type
TPureInterfacedObject = class(TObject, IInterface)
protected
{ IInterface }
function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
{ TPureInterfacedObject }
function TPureInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NOINTERFACE;
end;
function TPureInterfacedObject._AddRef: Integer;
begin
Result := -1;
end;
function TPureInterfacedObject._Release: Integer;
begin
Result := -1;
end;
There is no such class, but you can easily write your own, as others have shown. I do, however, wonder why you would need it. In my experience, there is seldom a real need for such a class, even if you want to mix object and interface references.
Also note that when you use such a class, you'll still have to take care of setting any interface references you have to such an object to nil before they leave scope and before you free the object. Otherwise you might get the situation the runtime tries to call _Release on a freed object, and that tends to cause an invalid pointer exception.
IOW, I would advise against using such a class at all.
As of Delphi 11 Embarcadero added TNoRefCountObject to the System unit. Here's the note from the release notes:
The new class System.TNoRefCountObject is a non-reference-counted
IInterface implementation (replacing the old and oddly named
TSingletonObject)
I currently use a record to pass several result parameters for a function and need to add some more data as it follows:
type
TItemType = (itFile, itRegistry);
TItemDetails = record
Success: Boolean;
ItemType: TItemType;
TotalCount: Integer;
TotalSize: Int64;
List: TStringList;
end;
function DoSomething: TItemDetails;
Is it possible/advisable to use a TStringList inside a record for this specific case?
I found on Embarcadero Developer Network a class that allows to declare StringList instead of TStringList and takes care of creating and freeing the list. Would this be an advisable solution?
http://cc.embarcadero.com/Item/25670
Also, if this does indeed works, will I have to manually free the TStringList?
Yes, by all means, just be aware that if the record goes out of scope, then it looses the reference to the object (unless you add code otherwise).
I've used that StringList example you are referring too, and that works great to have a record manage the lifetime of a TStringList. You can adapt that to your usage. The key is the embedded Interface which frees the object when it goes out of scope with the record.
You can also look at Allen Bauer's Nullable record example. I included the code, but you will want to read the article (and comments) too. It uses Generics in Delphi 2009 or newer, but you can adapt it to earlier versions of Delphi. Again the key is the interface, but he takes a different approach.
unit Foo;
interface
uses Generics.Defaults, SysUtils;
type
Nullable<T> = record
private
FValue: T;
FHasValue: IInterface;
function GetValue: T;
function GetHasValue: Boolean;
public
constructor Create(AValue: T);
function GetValueOrDefault: T; overload;
function GetValueOrDefault(Default: T): T; overload;
property HasValue: Boolean read GetHasValue;
property Value: T read GetValue;
class operator NotEqual(ALeft, ARight: Nullable<T>): Boolean;
class operator Equal(ALeft, ARight: Nullable<T>): Boolean;
class operator Implicit(Value: Nullable<T>): T;
class operator Implicit(Value: T): Nullable<T>;
class operator Explicit(Value: Nullable<T>): T;
end;
procedure SetFlagInterface(var Intf: IInterface);
implementation
function NopAddref(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopRelease(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
Result := E_NOINTERFACE;
end;
const
FlagInterfaceVTable: array[0..2] of Pointer =
(
#NopQueryInterface,
#NopAddref,
#NopRelease
);
FlagInterfaceInstance: Pointer = #FlagInterfaceVTable;
procedure SetFlatInterface(var Intf: IInterface);
begin
Intf := IInterface(#FlagInterfaceInstance);
end;
{ Nullable<T> }
constructor Nullable<T>.Create(AValue: T);
begin
FValue := AValue;
SetFlagInterface(FHasValue);
end;
class operator Nullable<T>.Equal(ALeft, ARight: Nullable<T>): Boolean;
var
Comparer: IEqualityComparer<T>;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer<T>.Default;
Result := Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue = ARight.HasValue;
end;
class operator Nullable<T>.Explicit(Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> nil;
end;
function Nullable<T>.GetValue: T;
begin
if not HasValue then
raise Exception.Create('Invalid operation, Nullable type has no value');
Result := FValue;
end;
function Nullable<T>.GetValueOrDefault: T;
begin
if HasValue then
Result := FValue
else
Result := Default(T);
end;
function Nullable<T>.GetValueOrDefault(Default: T): T;
begin
if not HasValue then
Result := Default
else
Result := FValue;
end;
class operator Nullable<T>.Implicit(Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
class operator Nullable<T>.Implicit(Value: T): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
class operator Nullable<T>.NotEqual(const ALeft, ARight: Nullable<T>): Boolean;
var
Comparer: IEqualityComparer<T>;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer<T>.Default;
Result := not Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue <> ARight.HasValue;
end;
end.
It will work, but you'll have to free it manually. And since records clean themselves up automatically when they go out of scope, and don't have destructors, making sure you do it right can be a hassle. You're better off not using objects in records. If you need a data type that contains objects, why not make it an object too?
Any solution for a record correctly lifetime-managing a string list object will involve an interface in one way or another. So why not return an interface from your function in the first place? Add properties to the interface, and for the consuming code it will look like record fields. It will allow you to easily add more "record fields" later on, and you can put arbitrarily complex code in the getters that return the values.
Another issue to be aware of, if you use sizeof to determine the memory footprint of the record, it will only include the size of a pointer for the TStringList. If you attempt to stream it out, the pointer which is stored will NOT be available to later instances, so you would have to ignore the pointer on the load and have another method to load the Tstringlist.
For example:
Procedure SaveRecToStream(Rec: TItemDetails ; Stream:tStream);
var
i : integer;
begin
Stream.Write(Rec,SizeOf(Rec)-SizeOf(tSTringList));
Rec.List.saveToStream(Stream);
end;
Procedure LoadRecFromStream(Rec: TItemDetails ; Stream:tStream);
var
i : integer;
begin
FillMemory(#Rec,SizeOf(Rec),0);
i := Stream.Read(rec,SizeOf(Rec)-SizeOf(tStringList));
if i <> SizeOf(Rec)-SizeOf(tStringList) then
Raise Exception.create('Unable to load record');
Rec.List := tStringlist.create;
Rec.List.LoadFromStream(Stream);
end;
This assumes that each stream contains exactly one record, and that the record variable passed to LoadRecFromStream does not contain a live tStringlist (if it was previously used it must be freed prior to the call or a leak occurs).
Why not use something like
type PStringList = ^TStringList;
type TMyFreakyRecord = record
PointerToAStringList : PStringList;
// some more code here
end;
...
var x : TMyFreakyRecord;
stringlist : TStringList;
begin
stringList := TStringlist.create;
stringList.Add('any data you wish');
x.PointertoaStringList := #stringlist;
// some more code here
end;
and access the record's string list like
procedure ProcedureThatPasses(AFreakyRecord: TFreakyRecord);
var i : integer;
begin
for i := 0 to AFreakyRecord.PointerToAStringList.count -1 do
// something with AFreakyRecord.PointerToAStringList[i];
end;
in order to transparently free the memory allocated you can create a TList variable in which you add every variable of type TStringList that is used inside a record,
var frmMain : TfrmMain;
MyJunkList : TList;
...
implementation
...
procedure clearjunk;
var i : integer;
o : TObject;
begin
for i := MyJunkList.count -1 downto 0 do begin
o := MyJunkList[i];
FreeandNil(o);
end;
MyJunkList.clear;
end;
...
initialization
MyJunkList := TList.Create;
finalization
clearjunk;
FreeAndNil(MyJunkList );
end. // end of unit
if this helps, don't hesitate to visit http://delphigeist.blogspot.com/