I'm trying to resolve this problem. It's weird because it doesn't throw a Stack Overflow error but an Access Violation error. (See code below.)
Whenever CallDestructor function is called, DestroyChildren is called. So it's a recursive function.
When I'm handling only a few objects it works fine. My trouble is when I have a lot of instances to destroy.
unit AggregationObject;
interface
uses
System.Classes, System.Generics.Collections, System.Contnrs;
type
IParentObject = Interface;
IChildObject = Interface
['{061A8518-0B3A-4A1C-AA3A-4F42B81FB4B5}']
procedure CallDestructor();
procedure ChangeParent(Parent: IParentObject);
End;
IParentObject = Interface
['{86162E3B-6A82-4198-AD5B-77C4623481CB}']
procedure AddChild(ChildObject: IChildObject);
function RemoveChild(ChildObject: IChildObject): Integer;
function ChildrenCount(): Integer;
procedure DestroyChildren();
End;
TName = type String;
TChildObject = class(TInterfacedPersistent, IChildObject)
protected
FParentObject: IParentObject;
public
constructor Create( AParent: IParentObject ); virtual;
{IChildObject}
procedure CallDestructor();
procedure ChangeParent(Parent: IParentObject);
end;
TParentObject = class(TInterfacedPersistent, IParentObject)
strict private
FChildren: TInterfaceList;
private
FName: TName;
public
constructor Create();
{Polimórficos}
procedure BeforeDestruction; override;
{IParentObject}
procedure AddChild(AChildObject: IChildObject);
function RemoveChild(AChildObject: IChildObject): Integer;
function ChildrenCount(): Integer;
procedure DestroyChildren();
property Name: TName read FName write FName;
end;
TAggregationObject = class(TChildObject, IParentObject)
private
FController: IParentObject;
function GetController: IParentObject;
public
constructor Create( AParent: IParentObject ); override;
destructor Destroy(); override;
{Controller implementation}
public
property Controller: IParentObject read GetController implements IParentObject;
end;
implementation
uses
System.SysUtils, Exceptions;
{ TChildObject }
procedure TChildObject.CallDestructor;
begin
Self.Free;
end;
procedure TChildObject.ChangeParent(Parent: IParentObject);
begin
if Self.FParentObject <> nil then
IParentObject( Self.FParentObject ).RemoveChild( Self );
Self.FParentObject := Parent;
if Parent <> nil then
Parent.AddChild( Self );
end;
constructor TChildObject.Create(AParent: IParentObject);
begin
if not (AParent = nil) then
begin
FParentObject := AParent;
FParentObject.AddChild( Self );
end;
end;
{ TParentObject }
procedure TParentObject.AddChild(AChildObject: IChildObject);
begin
if (FChildren = nil) then FChildren := TInterfaceList.Create();
FChildren.Add( AChildObject );
end;
procedure TParentObject.BeforeDestruction;
begin
inherited;
DestroyChildren();
end;
function TParentObject.ChildrenCount: Integer;
begin
Result := -1;
if Assigned(FChildren) then
Result := FChildren.Count;
end;
constructor TParentObject.Create;
begin
FName := 'NoName';
end;
procedure TParentObject.DestroyChildren;
var
Instance: IChildObject;
begin
while FChildren <> nil do
begin
Instance := FChildren.Last as IChildObject;
if Instance <> nil then
begin
if RemoveChild( Instance ) > -1 then
begin
try
Instance.CallDestructor();
except on E: Exception do
raise EChildAlReadyDestroyed.Create('Parent: ' + Self.FName + #13#10 + E.Message);
end;
end;
end;
end;
end;
function TParentObject.RemoveChild(AChildObject: IChildObject): Integer;
begin
Result := -1;{if has no children}
if (FChildren <> nil) then
begin
Result := 0;{ Index 0}
if ( ( FChildren.Items[0] as IChildObject) = AChildObject) then
FChildren.Delete(0)
else
Result := FChildren.RemoveItem( AChildObject, TList.TDirection.FromEnd );
if (FChildren.Count = 0) then
begin
FreeAndNil( FChildren );
end;
end;
end;
{ TAggregationObject }
constructor TAggregationObject.Create(AParent: IParentObject);
begin
inherited Create(AParent);
FController := TParentObject.Create();
( FController as TParentObject ).Name := Self.ClassName + '_Parent';
end;
destructor TAggregationObject.Destroy;
begin
( FController as TParentObject ).Free;
inherited;
end;
function TAggregationObject.GetController: IParentObject;
begin
Result := FController;
end;
end.
OP managed to identify the problem, but hasn't posted an answer. I provide an edited version of his comment and add a more detailed explanation.
I think the problem was with mixing object reference and interface. Even though my objects aren't controlled by RefCount something hapens backstage: "However, due to the nature of interface references, _AddRef and _Release are still going to be called when the reference goes out of scope. If the class has been destroyed prior to that time, then you have an AV in _IntfClear." My last call in stack is _IntfClear or _IntfCopy. I think this is the problem. I'm not sure about how to correct that, so I've changed to an abstract class.
The Access Violations aren't caused by mixing object references and interfaces; there are ways to do this safely.
But they are caused by the fact that Delphi attempts to _Release a reference on an object that has already been destroyed.
However this raises the question: "Why does the AV only happen sometimes, and not all the time?"
To explain, I'm going to talk about an illegal memory operation. By this I mean a piece of code (or object) that accesses memory it is not supposed to.
You don't get an AV every time your program performs an illegal memory operation. An AV will only be raised if the illegal memory operation is noticed! There are 2 main reasons it might be unnoticed:
It may be "illegal" for one object in your program to access certain memory, but if it is legal for another instance to access that memory - then there is no way for the system to notice that you've actually committed an illegal memory operation.
Most of the time, FastMem requests memory from the OS in larger "pages" than what you actually request from FastMem. It then keeps track of multiple smaller allocations on the page. The page is only returned to the OS when there are no smaller allocations left on the page. Therefore again, the OS won't notice any illegal memory operations on a page still allocated to your program.
The second reason above is why a small number of objects doesn't cause an AV: The page on which the object was allocated is still allocated to your program.
But when you have a large number of instances: sometimes when you destroy an object, it the last one on a page; and the page is returned to the OS... Therefore you get AV when _Release is called on that page.
So, how do you fix it?
Well, the option you chose (use an abstract class instead of an interface) works. But you lose the benefits of interfaces. However, I would suggest not trying to manually control the destruction of interface objects. One of the benefits of interface references is that the underlying objects will self-destruct (if you let them).
I suspect you're doing this because you're mixing object references and interface references. So instead of forcing your interfaces behave like objects (and you've gone to a lot of trouble to do so), rather simply let each of your object references manually add a reference to the interface. You can do this with the following code:
(ObjectRef as IUnkown)._AddRef;
//Do stuff with ObjectRef
(ObjectRef as IUnkown)._Release;
SIDE NOTE:
You found it weird that no Stack Overflow error was raised. (And obviously you figured out why the AV was raised.) I'd like to point out that typically recursion will only trigger SO errors: if the recursion is very deep (and I mean very); or if each recursion allocates a rather large amount of memory on the stack.
The detail was the difference.
TValueObject is a specialization of TAggregationObject and it implements IMasterValue, something like this:
IMasterValue = interface
//GUID Here
function MasterValue: variant;
end;
TValueObject = class(TAggregationObject , IMasterValue)
public
function MasterValue: variant;
end;
So I have:
TSomeService = class
public
function Find(AMasterValue: IMasterValue): TValueObject;
end;
procedure DoSome(AValueObject: TValueObject);
begin
with TSomeService.Create() do
begin
try
Find(AValueObject); //This will get cleared when method exits
finally
AValueObject.Free(); //But the object is destroyed before that
end;
end;
end;
//Occurs on great concurrency because the memory will be reused, otherwise the memory is still there hidding the problem. The threads running loop for destruction will show the problem.
The workaround for that, is:
procedure DoSome(AValueObject: TValueObject);
var
LMasterValue: IMasterValue;
begin
with TSomeService.Create() do
begin
try
LMasterValue := AValueObject;
try
Find(LMasterValue);
finally
LMasterValue := nil;
end;
finally
AValueObject.Free();
end;
end;
end;
Related
This may be an extension of my previous question.
I have understood that an interface-based variable can not be defined as its original type, otherwise the reference count does not work properly for automatic release.
But if a class implements two interfaces, then what type should be defined when make an instance of it?
Consider following code:
program Project2;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, Classes;
type
ITestInterface = interface(IInvokable)
['{A7BDD122-7DC6-4F23-93A2-B686571AB2C8}']
procedure TestMethod;
end;
IAnotherInterface = interface(IInvokable)
['{15FEC4A7-E361-41D0-9D52-170AFAD1794B}']
procedure AnotherMethod;
end;
TTestObj = class(TInterfacedObject, ITestInterface, IAnotherInterface)
constructor Create;
destructor Destroy; override;
private
FData: TStrings;
public
procedure TestMethod;
procedure AnotherMethod;
end;
{ TTestObj }
constructor TTestObj.Create;
begin
FData := TStringList.Create;
end;
destructor TTestObj.Destroy;
begin
Writeln('Destroy');
FData.Free;
inherited;
end;
procedure TTestObj.TestMethod;
begin
FData.Text := 'TestMethod';
Writeln(FData.Strings[0]);
end;
procedure TTestObj.AnotherMethod;
begin
FData.Text := 'AnotherMethod';
Writeln(FData.Strings[0]);
end;
{ Main }
function CreateObj: TTestObj;
begin
Result := TTestObj.Create;
end;
function CreateObj_i1: ITestInterface;
begin
Result := TTestObj.Create;
end;
function CreateObj_i2: IAnotherInterface;
begin
Result := TTestObj.Create;
end;
procedure Main;
var
TestObj: ITestInterface; // It must be declared as an interface type, or it won't be freed correctly.
AnotherObj: IAnotherInterface;
NaturalObj: TTestObj;
begin
{ 1st way: The syntax is a bit natural, but easily lead to memory leaks. }
CreateObj; // memory leak !
TestObj := CreateObj;
TestObj.TestMethod;
AnotherObj := CreateObj;
AnotherObj.AnotherMethod;
TestObj := nil;
AnotherObj := nil;
Writeln('----------');
{ 2nd way: The syntax is a bit messy, you should do type conversion carefully. }
CreateObj_i1; // object freed correctly.
TestObj := TTestObj(CreateObj_i2); // Using ITestInterface(CreateObj_i2) is wrong.
TestObj.TestMethod;
AnotherObj := TTestObj(CreateObj_i1); // Using IAnotherInterface(CreateObj_i1) is wrong.
AnotherObj.AnotherMethod;
TestObj := nil; // useless, it won't be be freed until the procedure returns.
AnotherObj := nil; // as above.
Writeln('----------');
{ 3rd way: The syntax is a bit natural, but it's easily lead to access violation if pass the `NaturalObj` out of the procedure. }
NaturalObj := TTestObj(CreateObj_i1); // Using TTestObj(CreateObj_i2) is okay too.
NaturalObj.TestMethod;
NaturalObj.AnotherMethod;
end;
begin
Writeln('Program start!');
Main;
Writeln('Program end.');
Readln;
end.
So which way is your preferred? Or any other advice? Thanks in advance.
There is a lot of confusion and complexity here. Rather than trying to dissect what you have, I'll show you how I would do it.
First of all remove all variables of type TTestObj. You should be using interface references only. You'll want a variable for each one.
var
TestIntf: ITestInterface;
AnotherIntf: IAnotherInterface;
Note that I have changed the name of these variables, replacing the Obj suffix with Intf. This reflects that they are interface references rather than object references.
Then you can simply do this:
TestIntf := TTestObj.Create;
AnotherIntf := TestIntf as IAnotherInterface;
Now you have two interface variables, one for each of your interfaces. It so happens that the implementing object behind both of these references is the same object, which is presumably what you want.
You could equally have reversed the logic:
AnotherIntf := TTestObj.Create;
TestIntf := AnotherIntf as ITestInterface;
This achieves exactly the same effect, you can do it either way.
If you want a different instance behind the variables then that is easy enough:
TestIntf := TTestObj.Create;
AnotherIntf := TTestObj.Create;
The key points here are:
Don't mix interfaces and objects. Once you start using an interface don't access the implementing object behind it.
When an object implements multiple interfaces, use the as operator to obtain the other interfaces.
I am having issue with declaring a unique global variable for each connection of IdTCPServer. What i am trying to do here is.
TMyContext = class(TIdServerContext)
public
Tag: Integer;
Queue: TIdThreadSafeList;
FPacketBuffer: Pointer;
PacketBufferPtr: Integer;
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;
end;
and then accessing the variable using TMyContext(AContext).FPacketBuffer, but i get an access violation error when there is a connection active and a new connection tries to connect. here is what is in my idTcpConnect and idTcpDisconnect
procedure TMainFrm.MainSckConnect(AContext: TIdContext);
begin
TMyContext(AContext).Queue.Clear;
TMyContext(AContext).Tag := -1;
GetMem(TMyContext(AContext).FPacketBuffer,65536);
end;
procedure TMainFrm.MainSckDisconnect(AContext: TIdContext);
Var Client: TClientInfo;
begin
//If TMyContext(AContext).Queue.Count > 0 Then TMyContext(AContext).Queue.Clear;
TMyContext(AContext).Queue.Clear;
FreeMem(TMyContext(AContext).FPacketBuffer);
If AContext.Data <> nil Then Begin
Client := Pointer(AContext.Data);
Clients.Remove(Client);
Client.Free;
AContext.Data := nil;
End;
end;
The error occures when getmem is called in idtcpconnect, i think i am doing it all wrong, i am not sure how i can have a unique global variable for each context.
Make sure you are assigning your class type to the TIdTCPServer.ContextClass property before activating the server at runtime, eg:
procedure TMainFrm.FormCreate(Sender: TObject);
begin
MainSck.ContextClass := TMyContext;
end;
You can't change the class of a [already created] object instance to a different type. The object is of the class it was instantiated at creation time.
You can safely cast any object to it's own class or any class it inherits of, because the object IS of that class. In a hard cast (like you're doing), you're telling the compiler you know what you're doing, for example:
type
TMyButton: TButton
public
FMyField: array[1..50] of byte;
end;
var
Button: TButton;
begin
//next line is valid, a variable of type TButton can reference any object
//inheriting from TButton or a TButton instance directly
Button := TMyButton.Create(nil);
//next line contains a valid cast, because Button contains a reference to
//a instance of TMyButton
TMyButton(Button).FMyField[10] := 5;
//valid, a TButton variable referencing a TButton instance
Button := TButton.Create(nil);
//next line is invalid and may cause an AV or in the worst case
//you may corrupt memory by doing that
TMyButton(AButton).FMyField[20] := 5;
end;
The fact is, in your OnConnect event, you get an already created instance of TIdContext (or a descendant type).
If you want this object to belong to your class, you have to first ask the server to create objects of that class, via the ContextClass property. You have to do this before the Active property of the server is set to true.
procedure TMyForm.Init;
begin
MyServer.ContextClass := TMyContext;
MyServer.Active := True;
end;
And finally, if you have object references, you have to create the objects on the context constructor, or add a Late create mechanism if you don't want to waste memory and you don't use it too often:
TMyContext = class(TIdServerContext)
private
FQueue: TIdThreadSafeList;
public
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
destructor Destroy; override;
property Queue: TIdThreadSafeList read FQueue;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil);
begin
inherited;
FQueue := TIdThreadSafeList.Create(Parameters);
end;
destructor TMyContext.Destroy;
begin
FQueue.Free;
inherited;
end;
PHEWWWW! i was killing myself about what is wrong, i was thinking that the FPacetBuffer variable isnt unique to each connection but after alot of debugging and commenting out code sections i saw the problem and i was like WHATTT!!!!
In processing a login packet data i declared a PChar variable and copied data using StrLCopy to it and retrieved the size of the data and then assigned a null character myself to it (and that was the problem line).
Size := (Packet.BufferSize-SizeOf(TLoginPacket));
GetMem(UserName,Size);
StrLCopy(UserName, PChar(Cardinal(Packet)+SizeOf(TLoginPacket)),Size);
UserName[Size] := #0; <--- This Line here
The size variable was holding real size + 2 in it.
Thanks for all the help guys :)
Following up on my earlier question :
Generics and Marshal / UnMarshal. What am I missing here?
In "part #1" (the link above) TOndrej provided a nice solution - that failed on XE2.
Here I provide corrected source to correct that.
And I feel the need to expand this issue a bit more.
So I would like to hear you all how to do this :
First - To get the source running on XE2 and XE2 update 1 make these changes :
Marshal.RegisterConverter(TTestObject,
function (Data: TObject): String // <-- String here
begin
Result := T(Data).Marshal.ToString; // <-- ToString here
end
);
Why ??
The only reason I can see must be related to XE2 is having a lot more RTTI information available. And hence it will try and marshal the TObject returned.
Am I on the right track here? Please feel free to comment.
More important - the example does not implement an UnMarshal method.
If anyone can produce one and post it here I would love it :-)
I hope that you still have interest in this subject.
Kind Regards
Bjarne
In addition to the answer to this question, I've posted a workaround to your previous question here: Generics and Marshal / UnMarshal. What am I missing here?
For some reason, using the non-default constructor of the TJsonobject causes the issue in XE2 - using the default constructor "fixed" the problem.
First, you need to move your TTestobject to its own unit - otherwise, RTTI won't be able to find/create your object when trying to unmarshal.
unit uTestObject;
interface
uses
SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections, DbxJson, DbxJsonReflect;
type
{$RTTI EXPLICIT METHODS([]) PROPERTIES([vcPublished]) FIELDS([vcPrivate])}
TTestObject=class(TObject)
private
aList:TStringList;
public
constructor Create; overload;
constructor Create(list: array of string); overload;
constructor Create(list:TStringList); overload;
destructor Destroy; override;
function Marshal:TJSonObject;
class function Unmarshal(value: TJSONObject): TTestObject;
published
property List: TStringList read aList write aList;
end;
implementation
{ TTestObject }
constructor TTestObject.Create;
begin
inherited Create;
aList:=TStringList.Create;
end;
constructor TTestObject.Create(list: array of string);
var
I:Integer;
begin
Create;
for I:=low(list) to high(list) do
begin
aList.Add(list[I]);
end;
end;
constructor TTestObject.Create(list:TStringList);
begin
Create;
aList.Assign(list);
end;
destructor TTestObject.Destroy;
begin
aList.Free;
inherited;
end;
function TTestObject.Marshal:TJSonObject;
var
Mar:TJSONMarshal;
begin
Mar:=TJSONMarshal.Create();
try
Mar.RegisterConverter(TStringList,
function(Data:TObject):TListOfStrings
var
I, Count:Integer;
begin
Count:=TStringList(Data).Count;
SetLength(Result, Count);
for I:=0 to Count-1 do
Result[I]:=TStringList(Data)[I];
end);
Result:=Mar.Marshal(Self) as TJSonObject;
finally
Mar.Free;
end;
end;
class function TTestObject.Unmarshal(value: TJSONObject): TTestObject;
var
Mar: TJSONUnMarshal;
L: TStringList;
begin
Mar := TJSONUnMarshal.Create();
try
Mar.RegisterReverter(TStringList,
function(Data: TListOfStrings): TObject
var
I, Count: Integer;
begin
Count := Length(Data);
Result:=TStringList.Create;
for I := 0 to Count - 1 do
TStringList(Result).Add(string(Data[I]));
end
);
//UnMarshal will attempt to create a TTestObject from the TJSONObject data
//using RTTI lookup - for that to function, the type MUST be defined in a unit
Result:=Mar.UnMarshal(Value) as TTestObject;
finally
Mar.Free;
end;
end;
end.
Also note that the constructor has been overloaded - this allows you to see that the code is functional without pre-pouplating the data in the object during creation.
Here is the implementation for the generic class list object
unit uTestObjectList;
interface
uses
SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections,
DbxJson, DbxJsonReflect, uTestObject;
type
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
TTestObjectList<T:TTestObject,constructor> = class(TObjectList<T>)
public
function Marshal: TJSonObject;
constructor Create;
class function Unmarshal(value: TJSONObject): TTestObjectList<T>; static;
end;
//Note: this MUST be present and initialized/finalized so that
//delphi will keep the RTTI information for the generic class available
//also, it MUST be "project global" - not "module global"
var
X:TTestObjectList<TTestObject>;
implementation
{ TTestObjectList<T> }
constructor TTestObjectList<T>.Create;
begin
inherited Create;
//removed the add for test data - it corrupts unmarshaling because the data is already present at creation
end;
function TTestObjectList<T>.Marshal: TJSonObject;
var
Marshal: TJsonMarshal;
begin
Marshal := TJSONMarshal.Create;
try
Marshal.RegisterConverter(TTestObjectList<T>,
function(Data: TObject): TListOfObjects
var
I: integer;
begin
SetLength(Result,TTestObjectlist<T>(Data).Count);
for I:=0 to TTestObjectlist<T>(Data).Count-1 do
Result[I]:=TTestObjectlist<T>(Data)[I];
end
);
Result := Marshal.Marshal(Self) as TJSONObject;
finally
Marshal.Free;
end;
end;
class function TTestObjectList<T>.Unmarshal(value: TJSONObject): TTestObjectList<T>;
var
Mar: TJSONUnMarshal;
L: TStringList;
begin
Mar := TJSONUnMarshal.Create();
try
Mar.RegisterReverter(TTestObjectList<T>,
function(Data: TListOfObjects): TObject
var
I, Count: Integer;
begin
Count := Length(Data);
Result:=TTestObjectList<T>.Create;
for I := 0 to Count - 1 do
TTestObjectList<T>(Result).Unmarshal(TJSONObject(Data[I]));
end
);
//UnMarshal will attempt to create a TTestObjectList<TTestObject> from the TJSONObject data
//using RTTI lookup - for that to function, the type MUST be defined in a unit,
//and, because it is generic, there must be a GLOBAL VARIABLE instantiated
//so that Delphi keeps the RTTI information avaialble
Result:=Mar.UnMarshal(Value) as TTestObjectList<T>;
finally
Mar.Free;
end;
end;
initialization
//force delphi RTTI into maintaining the Generic class information in memory
x:=TTestObjectList<TTestObject>.Create;
finalization
X.Free;
end.
There are several things that are important to note:
If a generic class is created at runtime, RTTI information is NOT kept unless there is a globally accessible object reference to that class in memory. See here: Delphi: RTTI and TObjectList<TObject>
So, the above unit creates such a variable and leaves it instantiated as discussed in the linked article.
The main procedure has been updated that shows both marshaling and unmarshaling the data for both objects:
procedure Main;
var
aTestobj,
bTestObj,
cTestObj : TTestObject;
aList,
bList : TTestObjectList<TTestObject>;
aJsonObject,
bJsonObject,
cJsonObject : TJsonObject;
s: string;
begin
aTestObj := TTestObject.Create(['one','two','three','four']);
aJsonObject := aTestObj.Marshal;
s:=aJsonObject.ToString;
Writeln(s);
bJsonObject:=TJsonObject.Create;
bJsonObject.Parse(BytesOf(s),0,length(s));
bTestObj:=TTestObject.Unmarshal(bJsonObject) as TTestObject;
writeln(bTestObj.List.Text);
writeln('TTestObject marshaling complete.');
readln;
aList := TTestObjectList<TTestObject>.Create;
aList.Add(TTestObject.Create(['one','two']));
aList.Add(TTestObject.Create(['three']));
aJsonObject := aList.Marshal;
s:=aJsonObject.ToString;
Writeln(s);
cJSonObject:=TJsonObject.Create;
cJSonObject.Parse(BytesOf(s),0,length(s));
bList:=TTestObjectList<TTestObject>.Unmarshal(cJSonObject) as TTestObjectList<TTestObject>;
for cTestObj in bList do
begin
writeln(cTestObj.List.Text);
end;
writeln('TTestObjectList<TTestObject> marshaling complete.');
Readln;
end;
Here is my own solution.
As I am very fond of polymorphism, I actually also want a solution that can be built into an object hierarchy. Lets say TTestObject and TTestObjectList is our BASE object. And from that we descend to TMyObject and also TMyObjectList. And furthermore I've made changes to both Object and List - added properties for Marshaller/UnMarshaller
TMyObject = class(TTestObject) and TMyObjectList<T:TMyObject> = class(TTestObjectList)
With this we now introduce some new problems. Ie. how to handle marshalling of different types between lines in the hierarchy and how to handle TJsonMarshal and TJsonUnMarshal as properties on TTestObject and List.
This can be overcome by introducing two new methods on TTestObject level. Two class functions called RegisterConverters and RegisterReverters. Then we go about and change the marshal function of TTestObjectList into a more simpel marshalling.
Two class functions and properties for both object and List.
class procedure RegisterConverters(aClass: TClass; aMar: TJSONMarshal); virtual;
class procedure RegisterReverters(aClass: TClass; aUnMar: TJSONUnMarshal); virtual;
property Mar: TJSONMarshal read FMar write SetMar;
property UnMar: TJSONUnMarshal read FUnMar write SetUnMar;
The Marshal function of List can now be done like this:
function TObjectList<T>.Marshal: TJSONObject;
begin
if FMar = nil then
FMar := TJSONMarshal.Create(); // thx. to SilverKnight
try
RegisterConverters; // Virtual class method !!!!
try
Result := FMar.Marshal(Self) as TJSONObject;
except
on e: Exception do
raise Exception.Create('Marshal Error : ' + e.Message);
end;
finally
ClearMarshal; // FreeAndNil FMar and FUnMar if assigned.
end;
end;
Sure we can still have a marshaller for our TTestObject - but the Marshal function of TTestObjectList will NOT use it. This way only ONE Marshaller will get created when calling Marshal of TTestObjectList (or descendants). And this way we end up getting marshalled ONLY the information we need to recreate our structure when doing it all backwards - UnMarshalling :-)
Now this actually works - but I wonder if anyone has any comments on this ?
Lets add a property "TimeOfCreation" to TMyTestObject:
property TimeOfCreation : TDateTime read FTimeOfCreation write FTimeOfCreation;
And set the property in the constructor.
FTimeofCreation := now;
And then we need a Converter so we override the virtual RegisterConverters of TTestObject.
class procedure TMyTestObject.RegisterConverters(aClass: TClass; aMar: TJSONMarshal);
begin
inherited; // instanciate marshaller and register TTestObject converters
aMar.RegisterConverter(aClass, 'FTimeOfCreation',
function(Data: TObject; Field: String): string
var
ctx: TRttiContext;
date: TDateTime;
begin
date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<TDateTime>;
Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
end);
end;
I end up with Very simple source like using TTestObject ie.
aList := TMyTestObjectList<TMyTestObject>.Create;
aList.Add(TMyTestObject.Create(['one','two']));
aList.Add(TMyTestObject.Create(['three']));
s := (aList.Marshal).ToString;
Writeln(s);
And now I have succeded in marshalling with polymorphism :-)
This also works with UnMarshalling btw. And Im in the process of rebuilding my FireBird ORM to produce source for all my objects like this.
The current OLD version can be found here :
http://code.google.com/p/objectgenerator/
Remember that it only works for FireBird :-)
When using Interfaces in Delphi and overriding reference counting, it is possible to bypass the_Release calls Delphi makes when an interface reaches a reference count of zero.
But - when mixing classes and interfaces (which is very useful) the _Release method is ALWAYS called no matter what. The problem is that in the sample code below, the local object is nill-ed, but _Release is still called - except on invalid memory. Depending on memory operations in the application, an exception can result when _Release is called on the nilled localObject's old location or no exception if the memory was not re-used.
So, can the compiler generated call to _Release be "removed/blocked/avoided/killed/redirected/vmt hijacked/terminated/smacked/etc etc etc"? If this can be achieved you have proper pure interfaces in Delphi.
unit TestInterfaces;
interface
uses
Classes,
SysUtils;
type
ITestInterface = interface
['{92D4D6E4-A67F-4DB4-96A9-9E1C40825F9C}']
procedure Run;
end;
TTestClass = class(TInterfacedObject, ITestInterface)
protected
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure Run;
end;
TRunTestClass = class(TObject)
protected
FlocalInterface : ITestInterface;
FlocalObject : TTestClass;
public
constructor Create;
destructor Destroy; override;
procedure Test;
end;
procedure RunTest;
procedure RunTestOnClass;
var
globalInterface : ITestInterface;
implementation
procedure RunTest;
var
localInterface : ITestInterface;
localObject : TTestClass;
begin
try
//create an object
localObject := TTestClass.Create;
//local scope
// causes _Release call when object is nilled
localInterface := localObject;
localInterface.Run;
//or global scope
// causes _Release call when exe shuts down - possibly on invalid memory location
globalInterface := localObject;
globalInterface.Run;
finally
//localInterface := nil; //--> forces _Release to be called
FreeAndNil( localObject );
end;
end;
procedure RunTestOnClass;
var
FRunTestClass : TRunTestClass;
begin
FRunTestClass := TRunTestClass.Create;
FRunTestClass.Test;
FRunTestClass.Free;
end;
{ TTheClass }
procedure TTestClass.Run;
begin
beep;
end;
function TTestClass._AddRef: Integer;
begin
result := -1;
end;
function TTestClass._Release: integer;
begin
result := -1;
end;
{ TRunTestClass }
constructor TRunTestClass.Create;
begin
FlocalObject := TTestClass.Create;
FlocalInterface := FlocalObject;
end;
destructor TRunTestClass.Destroy;
begin
//..
FlocalObject.Free;
//FlocalObject := nil;
inherited;
end;
procedure TRunTestClass.Test;
begin
FlocalInterface.Run;
end;
end.
There's no practical way to achieve what you are looking for. The compiler is going to emit the calls to _Release and in order to whack them you would need to find all the call sites. That's not practical.
I'm afraid the only viable approach when reference counted lifetime management is disabled is to ensure that you finalize (i.e. set to nil) all your interface references before calling Free.
When you use Interfaces you do not need to free your objects any more. interfaced objects will released automatically when there is no any references to same object.
In your sample you must delete _Release and _Addref functions in TTestClass they are defined in TInterfacedObject class.
In RunTest procedure you not need to Free the localObject only in finally section set globalInterface to nil. after end of procedure localInterface will destroy the local object automatically.
try
... use your code
...
finnaly
globalInnterface := nil;
end;
And about TTestRun.Destroy just left this destructor blank. you must not Free the FlocalObject.
TTestRun.Destroy;
begin
inherited;
end;
Hi
I am trying to do design patterns in Delphi and, since I couldn't find a reference material that I like in Delphi, I am converting the patterns I have in the O’Reilly C# 3.0 Design Patterns book. But this is not the problem. I have created the Proxy pattern from this book but there are some concepts of Delphi interfaces, constructors and destructor and general object lifetime and behavior that I apparently don't understand.
First I will post my code:
unit Unit2;
interface
uses
SysUtils;
type
ISubject = interface
['{78E26A3C-A657-4327-93CB-F3EB175AF85A}']
function Request(): string;
end;
TSubject = class
public
function Request(): string;
constructor Create();
end;
TProxy = class (TInterfacedObject, ISubject)
private
FSubject: TSubject;
public
function Request(): String;
destructor Destroy(); override;
end;
TProtectionProxy = class (TInterfacedObject, ISubject)
private
FSubject: TSubject;
FPassword: String;
public
constructor Create();
destructor Destroy(); override;
function Authenticate(supplied: String): String;
function Request(): String;
end;
implementation
{ TSubjectAccessor.TProxy }
destructor TProxy.Destroy;
begin
if Assigned(Self.FSubject) then
FreeAndNil(Self.FSubject);
inherited;
end;
function TProxy.Request: String;
begin
if not Assigned(Self.FSubject) then begin
WriteLn('Subject Inactive');
Self.FSubject := TSubject.Create();
end;
WriteLn('Subject active');
Result := 'Proxy: Call to ' + Self.FSubject.Request();
end;
{ TSubject }
constructor TSubject.Create;
begin
inherited;
end;
function TSubject.Request: string;
begin
Result := 'Subject Request Choose left door' + #10;
end;
{ TProtectionProxy }
function TProtectionProxy.Authenticate(supplied: String): String;
begin
if (supplied <> Self.FPassword) then begin
Result := 'Protection proxy: No Access!';
end else begin
Self.FSubject := TSubject.Create();
Result := 'Protection Proxy: Authenticated';
end;
end;
constructor TProtectionProxy.Create;
begin
Self.FPassword := 'Abracadabra';
end;
destructor TProtectionProxy.Destroy;
begin
if Assigned(Self.FSubject) then
FreeAndNil(Self.FSubject);
inherited;
end;
function TProtectionProxy.Request: String;
begin
if not Assigned(Self.FSubject) then begin
Result := 'Protection Proxy: Authenticate first!';
end else begin
Result := 'Protection Proxy: Call to ' + Self.FSubject.Request();
end;
end;
end.
These are the interfaces and classes used in the pattern. Next, is the code that uses these types:
program Structural.Proxy.Pattern;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit2 in 'Unit2.pas';
var
subject: ISubject;
begin
ReportMemoryLeaksOnShutdown := DebugHook <> 0;
try
WriteLn('Proxy Pattern' + #10);
try
subject := TProxy.Create();
WriteLn(subject.Request());
WriteLn(subject.Request());
subject := TProtectionProxy.Create();
WriteLn(subject.Request());
WriteLn(TProtectionProxy(subject).Authenticate('Secret'));
WriteLn(TProtectionProxy(subject).Authenticate('Abracadabra'));
WriteLn(subject.Request());
ReadLn;
finally
end;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
Is it legal to just assign a new object instance against an interface variable? I see in debugging that the constructor for TProtectionProxy is executed first and then a destructor for TProxy.
After the TProtectionProxy is created, Authenticate('Abracadabra') should be validated in logic but in debugger the FPassword is empty while it was assigned in the constructor? This one is very puzzling. But when I close the application, in the destructor, the password is present?
TProtectionProxy(subject) is ok but I read that is not recommended but (subject as TProtectionProxy) was not compiling for some reason (Operator not applicable...)?
I have added destructors because of the FSubject field. Is that ok?
Can a field variable be initiated on the same line where it is declared or I need to initiate in the constructor like in TProtectionProxy?
I know it is a lot I am asking here but I don't know anyone personally who knows Delphi OOP so well that I can ask.
Thank you.
This is the new version that works well for me. Thank you for all your help.
unit Unit2;
interface
uses
SysUtils;
type
ISubject = interface
['{78E26A3C-A657-4327-93CB-F3EB175AF85A}']
function Request(): string;
end;
IProtected = interface
['{928BA576-0D8D-47FE-9301-DA3D8F9639AF}']
function Authenticate(supplied: string): String;
end;
TSubject = class
public
function Request(): string;
end;
TProxy = class (TInterfacedObject, ISubject)
private
FSubject: TSubject;
public
function Request(): String;
destructor Destroy(); override;
end;
TProtectionProxy = class (TInterfacedObject, ISubject, IProtected)
private
FSubject: TSubject;
const FPassword: String = 'Abracadabra';
public
destructor Destroy(); override;
function Authenticate(supplied: String): String;
function Request(): String;
end;
implementation
{ TSubjectAccessor.TProxy }
destructor TProxy.Destroy;
begin
if Assigned(FSubject) then
FreeAndNil(FSubject);
inherited;
end;
function TProxy.Request: String;
begin
if not Assigned(FSubject) then begin
WriteLn('Subject Inactive');
FSubject := TSubject.Create();
end;
WriteLn('Subject active');
Result := 'Proxy: Call to ' + FSubject.Request();
end;
{ TSubject }
function TSubject.Request: string;
begin
Result := 'Subject Request Choose left door' + #10;
end;
{ TProtectionProxy }
function TProtectionProxy.Authenticate(supplied: String): String;
begin
if (supplied <> FPassword) then begin
Result := 'Protection proxy: No Access!';
end else begin
FSubject := TSubject.Create();
Result := 'Protection Proxy: Authenticated';
end;
end;
destructor TProtectionProxy.Destroy;
begin
if Assigned(FSubject) then
FreeAndNil(FSubject);
inherited;
end;
function TProtectionProxy.Request: String;
begin
if not Assigned(FSubject) then begin
Result := 'Protection Proxy: Authenticate first!';
end else begin
Result := 'Protection Proxy: Call to ' + FSubject.Request();
end;
end;
end.
and the program code:
program Structural.Proxy.Pattern;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit2 in 'Unit2.pas';
var
subject: ISubject;
protect: IProtected;
begin
ReportMemoryLeaksOnShutdown := DebugHook <> 0;
try
WriteLn('Proxy Pattern' + #10);
try
subject := TProxy.Create();
WriteLn(subject.Request());
WriteLn(subject.Request());
subject := nil;
subject := TProtectionProxy.Create();
WriteLn(subject.Request());
if Supports(subject, IProtected, protect) then begin
WriteLn(protect.Authenticate('Secret'));
WriteLn(protect.Authenticate('Abracadabra'));
end;
WriteLn(subject.Request());
ReadLn;
finally
end;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
I have removed all the constructors cause now they really don't do anything. And the default parametherless constructors are inherited from TInrefacedObject, correct?
I have left Self, I would like to hear why this shouldn't be used?
thank you
I have the full pattern implementation on http://delphipatterns.blog.com/2011/02/22/proxy-2/
You are not saying what version of Delphi you are using. The code you have given is only valid in Delphi XE and produces the following (correct) output there:
Proxy Pattern
Subject Inactive
Subject active
Proxy: Call to Subject Request Choose left door
Subject active
Proxy: Call to Subject Request Choose left door
Protection Proxy: Authenticate first!
Protection proxy: No Access!
Protection Proxy: Authenticated
Protection Proxy: Call to Subject Request Choose left door
If you look at the generated machine code:
Project2.dpr.25: WriteLn(TProtectionProxy(subject).Authenticate('Secret'));
004122C2 A1788E4100 mov eax,[$00418e78]
004122C7 8B154CF84000 mov edx,[$0040f84c]
004122CD E8E22BFFFF call #SafeIntfAsClass
004122D2 8D4DE0 lea ecx,[ebp-$20]
004122D5 BA38244100 mov edx,$00412438
004122DA E875D9FFFF call TProtectionProxy.Authenticate
004122DF 8B55E0 mov edx,[ebp-$20]
004122E2 A1EC3C4100 mov eax,[$00413cec]
004122E7 E8BC24FFFF call #Write0UString
004122EC E82F25FFFF call #WriteLn
004122F1 E82A1CFFFF call #_IOTest
You can see how the compiler first generates a call to SafeIntfAsClass which is used to get from an ISubject pointer to a pointer for the object that is implementing ISubject. Then TProtectionProxy.Authenticate is being called with this (correct) Self pointer.
If you try to run the same code with older versions of Delphi, this will fail:
var
subject: ISubject;
begin
...
subject := TProtectionProxy.Create();
WriteLn(subject.Request());
WriteLn(TProtectionProxy(subject).Authenticate('Secret'));
Older versions of Delphi did not support safely casting from an interface back to an object. What happens then is that the compiler simply takes the value of the subject variable, and calls TProtectionProxy.Authenticate with it.
The call itself succeeds because TProtectionProxy.Authenticate is a simple static method, not a virtual method, so the compiler just generates a call to an absolute address for it. But inside TProtectionProxy.Authenticate, Self is then wrong. Because the subject pointer is different from the object pointer for the TProtectionProxy that's implementing ISubject.
The correct solution for older delphi versions is to introduce an additional interface:
type
IProtection = interface
['{ACA182BF-7675-4346-BDE4-9D47CA4ADBCA}']
function Authenticate(supplied: String): String;
end;
...
TProtectionProxy = class (TInterfacedObject, ISubject, IProtection)
...
var
subject: ISubject;
protection: IProtection;
...
subject := TProtectionProxy.Create();
WriteLn(subject.Request());
if Supports(subject, IProtection, protection) then begin
WriteLn(protection.Authenticate('Secret'));
WriteLn(protection.Authenticate('Abracadabra'));
end else
WriteLn('IProtection not supported!');
WriteLn(subject.Request());
Generally speaking, you should never mix object and interface based access. Once you got an interface reference to an object, you shouldn't keep any object references to it (because the object will get automatically freed whenever the last interface reference goes out of scope somewhere). And even though Delphi XE allows you to correctly cast back from an interface to an object, that is something you should use very very carefully.
Is it legal to just assign a new object instance against an interface variable?
Yes. More than that, it is the right way to use interfaces in Delphi.
I see in debugging that the constructor for TProtectionProxy is executed first and then a destructor for TProxy.
Does it make any change for you? That is implementation details.
If you want to destroy TProxy object first assign subject to nil:
subject := TProxy.Create();
WriteLn(subject.Request());
WriteLn(subject.Request());
subject := nil;
subject := TProtectionProxy.Create();
..
After the TProtectionProxy is created, Authenticate('Abracadabra') should be validated in logic but in debugger the FPassword is empty while it was assigned in the constructor? This one is very puzzling.
I don't see it. FPassword is assigned as it should be.
But when I close the application, in the destructor, the password is present?
that is because subject is global variable. You can assign it to nil to force the object destruction manually before calling readln:
Subject:= nil;
Readln;
TProtectionProxy(subject) is ok but I read that is not recommended but (subject as TProtectionProxy) was not compiling for some reason (Operator not applicable...)?
I don't understand what are you trying to do. Both TProtectionProxy(subject) and (subject as TProtectionProxy) code does not seem sound.
I have added destructors because of the FSubject field. Is that ok?
Yes, you should destroy FSubject object instance in the destructors.
Can a field variable be initiated on the same line where it is declared or I need to initiate in the constructor like in TProtectionProxy?
No, you should initiate FPassword in the constructor as you did.
If you are not going to change FPassword you can declare it as constant:
TProtectionProxy = class (TInterfacedObject, ISubject)
private
FSubject: TSubject;
const FPassword: String = 'Abracadabra';
public
constructor Create();
destructor Destroy(); override;
function Authenticate(supplied: String): String;
function Request(): String;
end;
And don't use Self - there is no need for it in your code.