Is it possible to obtain RTTI information about a TMethod?
I can get the instance by
Instance := TObject(Method.Data);
so I can get the RTTI type of the instance, but how can I get the correct TRttiMethod? I want to check for attributes on a method passed in using a method pointer.
This approach works in theory, and there's a good change it will work in practice, but there are a couple of things that could prevent you from getting hold of the TRttiMethod.
The TMethod record says Data: Pointer, not TObject. This implies there might be a possibility of having something other then an TObject as the Data! This is a serious issue, because if the Data is not TObject, then attempting to extract RTTI from it is going to result in runtime errors.
Not all methods have RTTI. By default methods in the private area do not have RTTI, and one can use the {$RTTI} to stop generating RTTI for public or published members as well.
Those two issues would not be a problem for the usual type of event implementations we have in Delphi (double-click on the name of the event in Object Inspector and fill in the code), but then again I don't think you're talking about "vanila" implementations. Not many people would decorate the default event handlers with Attributes!
Code that demonstrates all of the above:
program Project15;
{$APPTYPE CONSOLE}
uses
SysUtils, RTTI;
type
// Closure/Event type
TEventType = procedure of object;
// An object that has a method compatible with the declaration above
TImplementation = class
private
procedure PrivateImplementation;
public
procedure HasRtti;
procedure GetPrivateImpEvent(out Ev:TEventType);
end;
TRecord = record
procedure RecordProc;
end;
// an object that has a compatible method but provides no RTTI
{$RTTI EXPLICIT METHODS([])}
TNoRttiImplementation = class
public
procedure NoRttiAvailable;
end;
procedure TImplementation.GetPrivateImpEvent(out Ev:TEventType);
begin
Ev := PrivateImplementation;
end;
procedure TImplementation.HasRtti;
begin
WriteLn('HasRtti');
end;
procedure TNoRttiImplementation.NoRttiAvailable;
begin
WriteLn('No RTTI Available');
end;
procedure TRecord.RecordProc;
begin
WriteLn('This is written from TRecord.RecordProc');
end;
procedure TImplementation.PrivateImplementation;
begin
WriteLn('PrivateImplementation');
end;
procedure TotalyFakeImplementation(Instance:Pointer);
begin
WriteLn('Totaly fake implementation, TMethod.Data is nil');
end;
procedure SomethingAboutMethod(X: TEventType);
var Ctx: TRttiContext;
Typ: TRttiType;
Method: TRttiMethod;
Found: Boolean;
begin
WriteLn('Invoke the method to prove it works:');
X;
// Try extract information about the event
Ctx := TRttiContext.Create;
try
Typ := Ctx.GetType(TObject(TMethod(X).Data).ClassType);
Found := False;
for Method in Typ.GetMethods do
if Method.CodeAddress = TMethod(X).Code then
begin
// Got the Method!
WriteLn('Found method: ' + Typ.Name + '.' + Method.Name);
Found := True;
end;
if not Found then
WriteLn('Method not found.');
finally Ctx.Free;
end;
end;
var Ev: TEventType;
R: TRecord;
begin
try
try
WriteLn('First test, using a method that has RTTI available:');
SomethingAboutMethod(TImplementation.Create.HasRtti);
WriteLn;
WriteLn('Second test, using a method that has NO rtti available:');
SomethingAboutMethod(TNoRttiImplementation.Create.NoRttiAvailable);
WriteLn;
WriteLn('Third test, private method, default settings:');
TImplementation.Create.GetPrivateImpEvent(Ev);
SomethingAboutMethod(Ev);
WriteLn;
WriteLn('Assign event handler using handler from a record');
try
SomethingAboutMethod(R.RecordProc);
except on E:Exception do WriteLn(E.Message);
end;
WriteLn;
WriteLn('Assign event handler using static procedure');
try
TMethod(Ev).Data := nil;
TMethod(Ev).Code := #TotalyFakeImplementation;
SomethingAboutMethod(Ev);
except on E:Exception do WriteLn(E.Message);
end;
WriteLn;
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
finally ReadLn;
end;
end.
Related
This already is reported as RSP-25603: "Exception.RaiseOuterException can cause wrong W1035 warning".
Given the following (demo) function F, I have changed an exception raising statement to now chain exceptions:
--- before
+++ after
## -1,11 +1,11 ##
function F(X: NativeInt): NativeInt;
begin
try
Result := 1 div X;
except
on EDivByZero do
- {ECustom}Exception.Create('...');
+ Exception.RaiseOuterException({ECustom}Exception.Create('...'));
else
raise;
end;
end;
Now, Ctrl-F9 gives the warning W1035:
[dcc32 Warning]: W1035 Return value of function 'F' might be undefined
However, all cases are handled. The compiler fails to recognize Exception.RaiseOuterException as the raise operation it is.
Unfortunately FAcquireInnerException: Boolean is private to the Exception class, not even to be set to True in derived custom classes which I could keep raising directly (raise ECustomException.Create).
Is there any way to make the compiler understand, while keeping the exceptions chained? Otherwise I can think of {$Warn No_RetVal Off}. How else could I work around this warning?
One way I can think of to avoid the warning, without disabling it, is to do the following instead:
function F(X: NativeInt): NativeInt;
begin
try
Result := 1 div X;
except
on E: Exception do
begin
if E is EDivByZero then
Exception.RaiseOuterException({ECustom}Exception.Create('...'));
raise;
end;
end;
end;
UPDATE: Another way, as stated in a comment, would be to simply define a return value that is not actually reached at runtime, eg:
function F(X: NativeInt): NativeInt;
begin
try
Result := 1 div X;
except
on E: EDivByZero do
begin
Exception.RaiseOuterException({ECustom}Exception.Create('...'));
Result := 0; // <-- just to keep the compiler happy
end;
end;
end;
EChainedException solution
(as requested by Max)
Update
I have put out a FR for this at Embarcadero. Please vote if you like this proposed solution. RSP-31679
By using this class, the inner exception is always recorded "as if" you had called Exception.RaiseOuterException. This allows you to use the simple raise statement, this avoids the warning message being issued by the compiler.
Useage
Just derive your custom exceptions from EChainedException instead of Exception, and use raise rather then Exception.RaiseOuterException.
Sourcecode
The relevant code is below. My complete EChainedException is a bit more complicated than this for supporting detection of fatal exceptions and stacktracing etc. If it doesn't compile, let me know what's missing and I'll add the missing part.
unit uChainedException;
interface
uses Sysutils;
{$M+} // ensures RTTI info is present for EChainedException
type
EChainedException = class(Exception)
protected
procedure RaisingException(P: system.sysutils.PExceptionRecord); override;
end;
implementation
uses rtti;
var // rtti pointers for handling the inner exception
vInnerExceptionOffset: NativeInt = -1;
vAcquireInnerExceptionOffset: NativeInt = -1;
vRunningInIDEInitialized: Boolean;
vRunningInIDE: Boolean;
function RunningInIDE:boolean;
begin
if not vRunningInIDEInitialized then
begin
vRunningInIDE:=AnsiSameText(ExtractFileName(ParamStr(0)),'BDS.EXE');
vRunningInIDEInitialized:=True;
end;
Result:=vRunningInIDE;
end;
procedure EChainedException.RaisingException(P: System.sysutils.PExceptionRecord);
var
PBoolean: ^Boolean;
PObject : ^TObject;
begin
if (ExceptObject<>self) and (vAcquireInnerExceptionOffset >=0) then
begin
PBoolean := Pointer(NativeInt(Self)+vAcquireInnerExceptionOffset);
PBoolean^ := PBoolean^ or not RunningInIDE;
end;
inherited;
// in some rare cases (like reraise exception from another thread)
// it may happen that the innerexception points to self
// this is corrected here.
if InnerException=self then
begin
PObject := Pointer(NativeInt(Self)+vInnerExceptionOffset);
PObject^ := nil;
end;
end;
procedure UnprepAutoInnerException;
begin
vInnerExceptionOffset:=-1;
vAcquireInnerExceptionOffset:=-1;
end;
procedure PrepAutoInnerException;
var
lRTTIContext: TRttiContext;
lInnerException:TRttiField;
lAcquireInnerException:TRttiField;
lClass: TRttiInstanceType;
begin
try
lRTTIContext.Create; //Notice vRTTIContext is a record, .Create initializes properties
try
lClass:=lRTTIContext.GetType(Exception) as TRttiInstanceType;
lInnerException:=lClass.GetField('FInnerException');
vInnerExceptionOffset := lInnerException.Offset;
lAcquireInnerException:=lClass.GetField('FAcquireInnerException');
vAcquireInnerExceptionOffset := lAcquireInnerException.Offset;
except
UnprepAutoInnerException;
raise;
end;
finally
lRTTIContext.Free;
end;
end;
initialization
PrepAutoInnerException;
finalization
UnprepAutoInnerException;
end.
Looking at this code I find it could use some modernizing, eg by using class vars instead of globals, and by using inline locale variables.
The entire unit is back from Delphi 6 days and contains many $ifdefs, and left out because it would surpass the answer.
I still wonder why exception chaining is not the default in delphi/rad studio like it is in other languages. Most likely because it would break existing code somehow.
I (also) answer my own question as I will take yet another approach. It provides for the following requirements:
I like to keep the raise statements, as they initially were,
so there won't be any necessary code changes here, and
which also means there won't be newly introduced warnings like W1035 or W1036.
I don't want to rebuild the inner RTL mechanics, however
I want to interfere with the RTL mechanics as little as possible.
I want to be flexible in controlling for chaining exceptions
sometimes forced or by default, on the exception implementation side, as well as
sometimes by argument, on the exception usage side, to extend functionality.
In my solution:
I accept to break through the Exception fields' visibility, FAcquireInnerException specifically.
I rely on RTTI to verify the fields' alignment (in ExceptionFields, according to Exception).
Here I provide a condensed implementation to copy-and-paste:
EException's constructor showcases the use of ExceptionFields:
ExceptionFields(Self).FAcquireInnerException := True;
-- to be used in any Exception-derived exception, and it will trigger the RTL mechanics to set the InnerException while it is raising the exception. Also, EException may serve as a common root for custom exception classes, if desired. Some constructors are reintroduced to be extended with const AcquireInnerException: Boolean = True, to hand-over the control to the caller while providing a default for the desired chaining.
Run ExceptionFields.VerifyFieldAlignments, if you want to verify the alignments of
the ("re-") declared externally accessible fields in ExceptionFields and
their (private) counterparts in Exception.
If it cannot verify this, it will raise an exception. It is run in EException's class constructor. Move it as propriate to you, if you do not use EException, yet want to keep the verification.
(Condensed) implementation:
unit Exceptions;
interface
uses
System.SysUtils;
type
EException = class (Exception)
public
class constructor Create;
constructor Create(const Msg: String; const AcquireInnerException: Boolean = True);
constructor CreateFmt(const Msg: String; const Args: array of const; const AcquireInnerException: Boolean = True); overload;
constructor CreateRes(const Msg: PResStringRec; const AcquireInnerException: Boolean = True);
constructor CreateResFmt(const Msg: PResStringRec; const Args: array of const; const AcquireInnerException: Boolean = True); overload;
end;
type
ExceptionFields = class (TObject)
{$Hints Off} // H2219
strict private
FMessage: String;
FHelpContext: Integer;
FInnerException: Exception;
FStackInfo: Pointer;
{$Hints On}
public
FAcquireInnerException: Boolean;
private
class procedure VerifyFieldAlignments;
end;
implementation
uses
System.Generics.Collections,
System.RTTI,
System.TypInfo;
{ ExceptionFields }
class procedure ExceptionFields.VerifyFieldAlignments;
procedure RaiseTypeNotFound(const ClassName: String);
begin
raise Exception.CreateFmt(
'Typ nicht gefunden: %s',
[ClassName]
);
end;
procedure RaiseFieldNotFound(const ClassName, FieldName: String);
begin
raise Exception.CreateFmt(
'Feld nicht gefunden: %s.%s',
[ClassName, FieldName]
);
end;
procedure RaiseFieldNotAligned(const LeftClassName: String; const LeftField: TPair<String, Integer>; const RightClassName: String; const RightField: TRTTIField);
begin
raise Exception.CreateFmt(
'Feld nicht ausgerichtet: %s.%s+%d (tatsächlich) vs. %s.%s+%d (erwartet)',
[
LeftClassName,
LeftField.Key,
LeftField.Value,
RightClassName,
RightField.Name,
RightField.Offset
]
);
end;
type
TMemberVisibilities = set of TMemberVisibility;
function GetDeclaredFields(const RTTIContext: TRTTIContext; const &Class: TClass; const IncludedVisibilities: TMemberVisibilities = [mvPublic, mvPublished]): TArray<TPair<String, Integer>>;
var
RTTIType: TRTTIType;
RTTIFields: TArray<TRTTIField>;
Index: NativeInt;
RTTIField: TRTTIField;
begin
RTTIType := RTTIContext.GetType(&Class);
if not Assigned(RTTIType) then
RaiseTypeNotFound(&Class.ClassName);
RTTIFields := RTTIType.GetDeclaredFields;
SetLength(Result, Length(RTTIFields));
Index := 0;
for RTTIField in RTTIFields do
if RTTIField.Visibility in IncludedVisibilities then
begin
Result[Index] := TPair<String, Integer>.Create(
RTTIField.Name,
RTTIField.Offset
);
Inc(Index);
end;
SetLength(Result, Index);
end;
const
Left: TClass = ExceptionFields;
Right: TClass = Exception;
var
RTTIContext: TRTTIContext;
DeclaredFields: TArray<TPair<String, Integer>>;
RTTIType: TRTTIType;
DeclaredField: TPair<String, Integer>;
RTTIField: TRTTIField;
begin
RTTIContext := TRTTIContext.Create;
try
DeclaredFields := GetDeclaredFields(RTTIContext, Left);
RTTIType := RTTIContext.GetType(Right);
if not Assigned(RTTIType) then
RaiseTypeNotFound(Right.ClassName);
for DeclaredField in DeclaredFields do
begin
RTTIField := RTTIType.GetField(DeclaredField.Key);
if not Assigned(RTTIField) then
RaiseFieldNotFound(Right.ClassName, DeclaredField.Key);
if DeclaredField.Value <> RTTIField.Offset then
RaiseFieldNotAligned(
Left.ClassName, DeclaredField,
RTTIType.Name, RTTIField
);
end;
finally
RTTIContext.Free;
end;
end;
{ EException }
class constructor EException.Create;
begin
inherited;
ExceptionFields.VerifyFieldAlignments;
end;
constructor EException.Create(const Msg: String;
const AcquireInnerException: Boolean);
begin
inherited Create(Msg);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
constructor EException.CreateFmt(const Msg: String;
const Args: array of const;
const AcquireInnerException: Boolean);
begin
inherited CreateFmt(Msg, Args);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
constructor EException.CreateRes(const Msg: PResStringRec;
const AcquireInnerException: Boolean);
begin
inherited CreateRes(Msg);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
constructor EException.CreateResFmt(const Msg: PResStringRec;
const Args: array of const;
const AcquireInnerException: Boolean);
begin
inherited CreateResFmt(Msg, Args);
ExceptionFields(Self).FAcquireInnerException := AcquireInnerException;
end;
end.
And a demo:
program ExceptionsDemo;
{$AppType Console}
{$R *.res}
uses
System.SysUtils,
Exceptions in 'Exceptions.pas';
type
EDemoException = class (EException)
end;
begin
try
try
try
raise EZeroDivide.Create('Level 3');
except
raise EException.Create('Level 2', False);
end;
except
raise EDemoException.Create('Level 1');
end;
except
on E: Exception do
begin
WriteLn(E.ClassName, ': ', E.Message);
while Assigned(E.InnerException) do
begin
E := E.InnerException;
WriteLn(E.ClassName, ': ', E.Message);
end;
end;
end;
ReadLn;
end.
Output -- the last line is only there on raise EException.Create('Level 2', True):
EDemoException: Level 1
EException: Level 2
EZeroDivide: Level 3
Thank you to all repliers!
The game i'm trying to make is snake, in the console application. I can get the snake to move along the screen however I am not sure how I can read the user inputing the keys WASD, code segment shown below.
write (StoredTrail); //This would be writing the snake, each segment is '[]'
repeat
clearScreen; // This is calling a clear screen procedure, if there is a simple way to make the snake disappear from the console that avoids such a lengthy procedure that would be great to know.
delete (StoredTrail ,0,2);
StoredTrail:= A+StoredTrail; //This makes the trail move along(A is ' ')
write(StoredTrail);
Xcord:= Xcord + 1;
sleep(150);
until 1=2;
I am also aware the sleep is very inefficient so if anyone had a better way to delay the movement of the snake that would also be welcomed. Coding for increasing the snakes length is also not implemented yet.
Many thanks to anyone able to help.
I give an example for a event driven console application, which update the screen iterativelly.
It would be too long to write here the user event handler routines and you can find it on a lot of places on the net. This is a fine example, which handle keyboard and mouse events as well:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils
, Vcl.ExtCtrls
;
type
TSnakeApp = class
private
fTimer : TTimer;
fExit : boolean;
protected
function createTimer : TTimer; virtual;
procedure releaseTimer; virtual;
procedure drawSnake( timer_ : TObject ); virtual;
procedure handleKeyBoardEvents; virtual;
public
constructor create;
destructor destroy; override;
procedure run;
end;
var
app : TSnakeApp;
function TSnakeApp.createTimer : TTimer;
begin
result := TTimer.Create( NIL );
end;
procedure TSnakeApp.releaseTimer;
begin
fTimer.Free;
end;
procedure TSnakeApp.drawSnake( timer_ : TObject );
begin
// if it takes too long time (>= times.interval), then disable+enable the timer
fTimer.enabled := FALSE;
try
finally
fTimer.enabled := TRUE;
end;
end;
procedure TSnakeApp.handleKeyBoardEvents;
begin
// It would be too long to write here, but you can find a very nice keyboard/mouse event handler for console applications here:
// https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
// case ( keyPressed ) of
// VK_ESC:
// fExit := TRUE;
// ...
end;
constructor TSnakeApp.create;
begin
inherited create;
fTimer := createTimer;
fTimer.Interval := 20;
fTimer.OnTimer := drawSnake;
end;
destructor TSnakeApp.destroy;
begin
releaseTimer;
inherited destroy;
end;
procedure TSnakeApp.run;
begin
fTimer.enabled := TRUE;
while ( not fExit ) do
begin
handleKeyBoardEvents;
end;
fTimer.enabled := FALSE;
end;
begin
try
try
app := TSnakeApp.create;
app.run;
finally
app.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
In the days of Turbo Pascal an ancient predecessor of Delphi there was a CRT unit that provided some useful functions for console applications. Two such functions that would be of interest to you for keyboard input are KeyPressed() which returns true if a key has been pressed and GetKey() which returns the key pressed. For Delphi itself there are a few sources of libraries that offer compatible functions. One is Rudy's Velthuis.Console unit.
I realise that Delphi does not support interface helpers, but after reading several SO topics and sources of Spring4D and so forth, I'm wondering is there is any way to achieve the following? The source code comments pretty much sums up what I'm trying to do, so here it is:
program IHelper;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Spring,
System.SysUtils;
type
IMyThing = interface
['{01E799A5-9141-4C5E-AA85-B7C9792024D9}']
procedure BasicThing;
end;
TMyThing = class(TInterfacedObject, IMyThing)
strict private
procedure BasicThing;
end;
IMyThingHelper = record
private
FOutage: IMyThing;
public
class operator Implicit(const Value: IMyThing): IMyThingHelper;
procedure HelpfulThing;
end;
TMyThingHelper = class helper for TMyThing
public
class procedure ObjectThing;
end;
{ TOutage }
procedure TMyThing.BasicThing;
begin
Writeln('Basic Thing');
end;
{ IOutageHelper }
procedure IMyThingHelper.HelpfulThing;
begin
Writeln('Helpful thing');
end;
class operator IMyThingHelper.Implicit(const Value: IMyThing): IMyThingHelper;
begin
Result.FOutage := Value;
end;
{ TMyThingHelper }
class procedure TMyThingHelper.ObjectThing;
begin
Writeln('Object thing');
end;
var
LThing: IMyThing;
begin
try
LThing := TMyThing.Create;
LThing.BasicThing;
//LThing.HelpfulThing; // <--- **** prefer this syntax but obviously does not compile
IMyThingHelper(LThing).HelpfulThing; // <--- this works ok but prefer not to have to cast it here
//LThing.ObjectThing; // <--- obviously does not compile
(LThing as TMyThing).ObjectThing; // <--- class helpers work ok but no good for interfaces
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Any ideas or suggestions on how this code could be made to work where shown with **** ?
I understand the answer might be an outright "no", but it seems that there's some pretty clever workarounds being done and perhaps someone much smarter than me knows how? (Delphi XE5)
Another example
var
dataObject: IDataObject;
//Get clipboard IDataObject
OleGetClipboard({out}dataObject);
//Check if they want us to move or copy what's on the clipboard
preferredDropEffect: DWORD := dataObject.GetPreferredDropEffect;
//...do the stuff with the clipboard
//Tell them what we did
dataObject.SetPerformedDropEffect(DROPEFFECT_NONE); //we moved the underlying data; sender need not do anything
dataObject.SetPasteSucceeded(DROPEFFECT_MOVE); //Paste complete
with a helper:
TDataObjectHelper = interface helper for IDataObject
public
function GetPreferredDropEffect(DefaultPreferredDropEffect: DWORD=DROPEFFECT_NONE): DWORD;
end;
function TDataObjectHelper.GetPreferredDropEffect(DefaultPreferredDropEffect: DWORD=DROPEFFECT_NONE): DWORD;
begin
{
DROPEFFECT_NONE = 0; //Drop target cannot accept the data.
DROPEFFECT_COPY = 1; //Drop results in a copy. The original data is untouched by the drag source.
DROPEFFECT_MOVE = 2; //Drag source should remove the data.
DROPEFFECT_LINK = 4; //Drag source should create a link to the original data.
DROPEFFECT_SCROLL = 0x80000000 //Scrolling is about to start or is currently occurring in the target. This value is used in addition to the other values.
}
if TDataObjectHelper.ContainsFormat(Source, CF_PreferredDropEffect) then
Result := TDataObjectHelper.GetUInt32(Source, CF_PREFERREDDROPEFFECT)
else
Result := DefaultDropEffect;
end;
Why not just use another interface?
program IHelper;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Spring,
System.SysUtils;
type
IMyThing = interface
['{01E799A5-9141-4C5E-AA85-B7C9792024D9}']
procedure BasicThing;
end;
IMyThingHelper = interface
['{...}']
procedure HelpfulThing;
end;
TMyThing = class(TInterfacedObject, IMyThing, IMyThingHelper)
strict private
procedure BasicThing;
procedure HelpfulThing;
end;
{ TOutage }
procedure TMyThing.BasicThing;
begin
Writeln('Basic Thing');
end;
{ IOutageHelper }
procedure TMyThing.HelpfulThing;
begin
Writeln('Helpful thing');
end;
var
LThing: IMyThing;
LHelper: IMyThingHelper;
begin
try
LThing := TMyThing.Create;
LThing.BasicThing;
if Supports(LThing, IMyThingHelper, LHelper) then
LHelper.HelpfulThing;
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
There are two ways of achieving this:
One would be having a variable of IMyThingHelper and assign your interface to it and then call the "extension method" on the record variable.
The other would be to use absolute:
var
LThing: IMyThing;
LHelper: IMyThingHelper absolute LThing;
begin
LThing := TMyThing.Create;
LHelper.HelpfulThing;
I blogged about this issue some while ago. Unfortunately in my case the "helper record" Enumerable<T> had so many generic methods that the compiler got slowed down immensely.
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 :-)
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.