Delphi call method based on RTTI information - delphi

Hey all, first sorry for my bad english.
Consider the following (not actual code):
IMyInterface = Interface(IInterfce)
procedure Go();
end;
MyClass = class(IMyInterface)
procedure Go();
end;
MyOtherClass = class
published
property name: string;
property data: MyClass;
end;
I'm setting "MyOtherClass" properties using RTTI. For the string property it's easy, but my question is:
How can I get a reference to the "data" (MyClass) property so I can call the Go() method?
I want to do something like this (pseudo-code):
for i:= 0 to class.Properties.Count
if (propertyType is IMyInterface) then
IMyInterface(class.properties[i]).Go()
(if only this was C# :( )
PS.: this is in delphi 7 (i know, even worse)

If the string property is easy, as you say, then I assume you're calling GetStrProp and SetStrProp from the TypInfo unit. Class-type properties can be equally easy with GetObjectProp and SetObjectProp.
if Supports(GetObjectProp(Obj, 'data'), IMyInterface, Intf) then
Intf.Go;
If you don't really need the interface, and you know that the data property has type TMyClass, then you can go a little more directly:
(GetObjectProp(Obj, 'data') as TMyClass).Go;
That requires the property to have a non-null value.
If you don't know the name of the property you want, then you can use some other things in TypInfo to search for it. For example, here is a function that will find all the published properties of an object that have values that implement IMyInterface; it calls Go on each of them in no particular order.
procedure GoAllProperties(Other: TObject);
var
Properties: PPropList;
nProperties: Integer;
Info: PPropInfo;
Obj: TObject;
Intf: IMyInterface;
Unk: IUnknown;
begin
// Get a list of all the object's published properties
nProperties := GetPropList(Other.ClassInfo, Properties);
if nProperties > 0 then try
// Optional: sort the list
SortPropList(Properties, nProperties);
for i := 0 to Pred(nProperties) do begin
Info := Properties^[i];
// Skip write-only properties
if not Assigned(Info.GetProc) then
continue;
// Check what type the property holds
case Info.PropType^^.Kind of
tkClass: begin
// Get the object reference from the property
Obj := GetObjectProp(Other, Info);
// Check whether it implements IMyInterface
if Supports(Obj, IMyInterface, Intf) then
Intf.Go;
end;
tkInterface: begin
// Get the interface reference from the property
Unk := GetInterfaceProp(Obj, Info);
// Check whether it implements IMyInterface
if Supports(Unk, IMyInterface, Intf) then
Intf.Go;
end;
end;
end;
finally
FreeMem(Properties);
end;
end;

You can get an array of all published propertied by calling GetPropInfos(MyClass.ClassInfo). This is an array of PPropInfo pointers. And you can get at type-specific data from a PPropInfo by calling GetTypeData on it, which returns a PTypeData. The record it points to will have the information you're looking for about the class reference.

Related

How to use "for/in" to find components inside of another component?

I'm refactoring a component code, and I found the follow code:
procedure TMenuToolbarButton.ClearActivation;
var
i: Integer;
begin
for i := 0 to Self.Parent.ComponentCount -1 do
begin
if (Self.Parent.Components[i] is TMenuToolbarButton) then
begin
(Self.Parent.Components[i] as TMenuToolbarButton).FActivatedImage.Visible := False;
(Self.Parent.Components[i] as TMenuToolbarButton).FActivatedImageGrowLeft.Visible := False;
end;
end;
end;
I'ts working perfectly today, but i want to use for/in in this method, something like this:
procedure TMenuToolbarButton.ClearActivation;
var
MyMenuToolbarButton: TMenuToolbarButton;
begin
for MyMenuToolbarButton in Self.Parent do
begin
MyMenuToolbarButton.FActivatedImage.Visible := False;
MyMenuToolbarButton.FActivatedImageGrowLeft.Visible := False;
end;
end;
I already tried with Generics.Collections casting the Self.Parent like this: TObjectList<TMenuToolbarButton>(Self.Parent)
So, I want to know if is there a better way to make the working code more "elegant"
The Parent is a TWinControl, not a TObjectList, so your attempted typecast is invalid.
You can't use a for.. in loop with the Components property directly, as it is not an iterable container that meets any of the documented requirements:
Delphi supports for-element-in-collection style iteration over containers. The following container iteration patterns are recognized by the compiler:
for Element in ArrayExpr do Stmt;
for Element in StringExpr do Stmt;
for Element in SetExpr do Stmt;
for Element in CollectionExpr do Stmt;
for Element in Record do Stmt;
The Components property is not an Array, a String, a Set, a Collection, or a Record, so it can't be iterated by a for..in loop.
However, TComponent itself satisfies the documented requirements of an iterable Collection:
To use the for-in loop construct on a class or interface, the class or interface must implement a prescribed collection pattern. A type that implements the collection pattern must have the following attributes:
The class or interface must contain a public instance method called GetEnumerator(). The GetEnumerator() method must return a class, interface, or record type.
The class, interface, or record returned by GetEnumerator() must contain a public instance method called MoveNext(). The MoveNext() method must return a Boolean. The for-in loop calls this method first to ensure that the container is not empty.
The class, interface, or record returned by GetEnumerator() must contain a public instance, read-only property called Current. The type of the Current property must be the type contained in the collection.
TComponent has a public GetEnumerator() method which returns a TComponentEnumerator object that internally iterates the Components property. But, since the property deals with TComponent objects, you will still have to manually typecast them inside the loop.
Try this:
procedure TMenuToolbarButton.ClearActivation;
var
//i: Integer;
Comp: TComponent;
Btn: TMenuToolbarButton;
begin
//for i := 0 to Self.Parent.ComponentCount -1 do
for Comp in Self.Parent do
begin
//Comp := Self.Parent.Components[i];
if Comp is TMenuToolbarButton then
begin
Btn := TMenuToolbarButton(Comp);
Btn.FActivatedImage.Visible := False;
Btn.FActivatedImageGrowLeft.Visible := False;
end;
end;
end;
So, using a for..in loop does not really gain you anything useful over a traditional for..to loop in this situation.
TComponent implements method GetEnumerator by returning instance of TComponentEnumerator, which enumerates all components owned by this component. In order to use this enumerator you could change your local variable declaration to var MyMenuToolbarButton: TComponent;, but you would still need to type-cast inside the loop.
If you really, really want to use for..in loop for enumerating components of specified type, you can write your own generic enumerator:
type
TComponentEnumerator<T: TComponent> = record
private
FIndex: Integer;
FComponent: TComponent;
public
constructor Create(AComponent: TComponent);
function GetCurrent: T; inline;
function GetEnumerator: TComponentEnumerator<T>;
function MoveNext: Boolean;
property Current: T read GetCurrent;
end;
constructor TComponentEnumerator<T>.Create(AComponent: TComponent);
begin
FIndex := -1;
FComponent := AComponent;
end;
function TComponentEnumerator<T>.GetCurrent: T;
begin
Result := T(FComponent.Components[FIndex]);
end;
function TComponentEnumerator<T>.GetEnumerator: TComponentEnumerator<T>;
begin
Result := Self;
end;
function TComponentEnumerator<T>.MoveNext: Boolean;
begin
Inc(FIndex);
while (FIndex < FComponent.ComponentCount) and (not (FComponent.Components[FIndex] is T)) do
Inc(FIndex);
Result := FIndex < FComponent.ComponentCount;
end;
Usage:
procedure TMenuToolbarButton.ClearActivation;
var
MyMenuToolbarButton: TMenuToolbarButton;
begin
for MyMenuToolbarButton in TComponentEnumerator<TMenuToolbarButton>.Create(Self.Parent) do
begin
MyMenuToolbarButton.FActivatedImage.Visible := False;
MyMenuToolbarButton.FActivatedImageGrowLeft.Visible := False;
end;
end;
Few notes:
This is pretty naïve implementation which is not protected against some edge cases like changing the components collection while iterating, cross-thread access, ... Of course your original code does none of that, but when you write general purpose class, you should consider making it more foolproof or document its limitations.
This implementation enumerates components of type T or its descendants.
Using enumerators adds small overhead when compared to simple for..to loop.

Delphi - Maintaining Self References to an Object

I've looked at many questions and resources which deal with the "Self" variable in an Object, but everyone says something different.
For example, in this question: Delphi Self-Pointer usage
the highest rated answer to the question appears to be wrong. Pointer(Self) does not point to the object which contains it, and cannot be used to pass references from inside the object.
I've tried doing this:
Type
myobject = class
PSelf: Pointer;
End;
Var
Obj: myobject;
Procedure RunProgram;
Begin
Obj := myobject.create;
Obj.PSelf := #Obj;
//Run the rest of the program
.
.
.
and for the most part, this has worked just fine.
My question is: is this a good coding practice? Can the "PSelf" variable be expected to point to the object for the duration of the program's execution?
I recently came across a bug where "PSelf" had stopped pointing to it's containing object, and I'm wondering if objects ever get shuffled around in the heap, or whether the memory had been corrupted.
Edit:
There is some instance in which using the "Self" variable didn't work for me, and now I cannot duplicate it. So this whole question is pointless, as is my technique of using a 'PSelf' variable. Sorry about that.
And as Ken pointed out, the link above has a correct answer :)
I think you're misunderstanding what Self is, and how object references work in Delphi.
A variable containing an instance of a class is already a pointer to that object instance. The Delphi compiler just allows you to leave out the dereference operator (^) as a convenience.
var
MyObject: TMyObject;
begin
MyObject := TMyObject.Create; // MyObject is now a pointer to an instance of TMyObject
...
Delphi also allows the shorthand of not using the dereference operator when accessing members or properties of the object instance. Again, the following code is actually equivalent:
MyObj.SomeProperty := SomeValue;
MyObj^.SomeProperty := SomeValue;
From the Delphi documentation:
A variable of a class type is actually a pointer that references an object. Hence more than one variable can refer to the same object. Like other pointers, class-type variables can hold the value nil. But you don't have to explicitly dereference a class-type variable to access the object it points to. For example, SomeObject.Size := 100 assigns the value 100 to the Size property of the object referenced by SomeObject; you would not write this as SomeObject^.Size := 100.
Self is an automatically declared property that points to the current instance of the object. In other words, it's automatically available inside the code that implements that class to reference the current instance of the object. This allows you to have multiple instances of the same object:
type
TMyObject=class(TObject)
private
FMyInteger: Integer;
function GetMyInteger: Integer;
procedure SetMyInteger(Value: Integer);
published
property MyInteger: Integer read GetMyInteger write SetMyInteger;
end;
...
function TMyObject.GetMyInteger: Integer;
begin
Result := Self.FMyInteger;
// Self is implied, so the above line can more simply be written as
// Result := FMyInteger;
end;
procedure TMyObject.SetMyInteger(Value: Integer);
begin
if (Value <> Self.FMyInteger) then // Self is again implied here
Self.FMyInteger := Value;
end;
var
MyObjOne, MyObjTwo: TMyObject;
i, j: Integer;
begin
MyObjOne := TMyObject;
// Here, the code inside TMyObject.SetInteger that
// uses `Self` would refer to `MyObjOne`
MyObjOne.MyInteger := 1;
MyObjTwo := TMyObject;
// Here, the code in TMyObject.SetInteger would be
// using the memory in `MyObjTwo` when using `Self`
MyObjTwo.MyInteger := 2;
end;
Note that Self is only valid in the code that implements the class. It's available and valid in TMyObject.GetMyInteger and TMyObject.SetMyInteger above (the only implemented code in my example), and always refers to the current instance.
There's no need to keep track of the addresses of Self, as the variable referencing that object instance is Self inside methods of that object instance. It's only valid inside that instance of the object, and always refers to that object instance. So in your code example, PSelf is just wasted space - myobject already contains a pointer to itself, and that pointer is automatically available in methods of myobject:
type
myobject = class; // Now automatically contains a `Self` pointer
// available in methods of the class once an
// instance is created
var
myobj: myobject;
begin
myobj := myobject.Create; // myobj.Self now is valid in methods of
// `myobject`
Here's my resolution to the problem. I'm still wondering why the second example doesn't work.
This works (but is the wrong way to do it):
Type
myobject1 = class(TObject)
PSelf: Pointer;
Number: Integer;
Function GiveReference: Pointer;
End;
pmyobject1: ^myobject1;
myobject2 = class(TObject)
p: pmyobject1;
End;
Var
Obj1: myobject1;
Obj2: myobject2;
Function myobject1.GiveReference: Pointer;
Begin
Result := PSelf;
End;
Procedure RunProgram;
Var
i: Integer;
Begin
Obj1 := myobject1.create;
Obj1.PSelf := #Obj1;
Obj2 := myobject2.create;
Obj2.P := Obj.GiveReference;
//to access 'number', this works
i := Obj2.P^.Number;
//Run the rest of the program
.
.
.
This does not work, but in my mind is exactly the same. This is what caused me to distrust the 'self' variable (even though yes, I was making a pointer to a pointer).
Type
myobject1 = class(TObject)
Number: Integer;
Function GiveReference: Pointer;
End;
pmyobject1: ^myobject1;
myobject2 = class(TObject)
p: pmyobject1;
End;
Var
Obj1: myobject1;
Obj2: myobject2;
Function myobject1.GiveReference: Pointer;
Begin
Result := #Self;
End;
Procedure RunProgram;
Var
i: Integer;
Begin
Obj1 := myobject1.create;
Obj2 := myobject2.create;
Obj2.P := Obj.GiveReference;
//This will fail, some of the time, but not all of the time.
//The pointer was valid while 'GiveReference' was being called, but
//afterwards, it is not valid.
i := Obj2.P^.Number;
//Run the rest of the program
.
.
.
And finally, this is what I should have been doing all along:
Type
myobject1 = class(TObject)
Number: Integer;
Function GiveReference: Pointer;
End;
myobject2 = class(TObject)
p: myobject1;
End;
Var
Obj1: myobject1;
Obj2: myobject2;
Function myobject1.GiveReference: Pointer;
Begin
Result := Self;
End;
Procedure RunProgram;
Var
i: Integer;
Begin
Obj1 := myobject1.create;
Obj2 := myobject2.create;
Obj2.P := Obj.GiveReference;
//No problems with this, although I would generally check if P was assigned prior to
//reading from it.
i := Obj2.P.Number;
//Run the rest of the program
Obj1.Free;
Obj2.P := nil;
I hadn't done this in the first place, because I was concerned that by not using a pointer, that I might actually be duplicating the entire object.

Can a record be used as a property of an object?

I'd like to make a record as an object's property. The problem is that when I change one of the fields of this record, the object isn't aware of the change.
type
TMyRecord = record
SomeField: Integer;
end;
TMyObject = class(TObject)
private
FSomeRecord: TMyRecord;
procedure SetSomeRecord(const Value: TMyRecord);
public
property SomeRecord: TMyRecord read FSomeRecord write SetSomeRecord;
end;
And then if I do...
MyObject.SomeRecord.SomeField:= 5;
...will not work.
So how do I make the property setting procedure 'catch' when one of the record's fields is written to? Perhaps some trick in how to declare the record?
More Info
My goal is to avoid having to create a TObject or TPersistent with an OnChange event (such as the TFont or TStringList). I'm more than familiar with using objects for this, but in an attempt to cleanup my code a little, I'm seeing if I can use a Record instead. I just need to make sure my record property setter can be called properly when I set one of the record's fields.
Consider this line:
MyObject.SomeRecord.SomeField := NewValue;
This is in fact a compile error:
[DCC Error]: E2064 Left side cannot be assigned to
Your actual code is probably something like this:
MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
What happens here is that you copy the value of the record type to the local variable MyRecord. You then modify a field of this local copy. That does not modify the record held in MyObject. To do that you need to invoke the property setter.
MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
MyObject.SomeRecord := MyRecord;
Or switch to using a reference type, i.e. a class, rather than a record.
To summarise, the problem with your current code is that SetSomeRecord is not called and instead you are modifying a copy of the record. And this is because a record is a value type as opposed to being a reference type.
Ultimately you will want to access the record's fields, yet as you propose, a record is often a suitable abstraction choice within a class. A class can neatly access the properties of a record as follows:
type
TMyRec = record
SomeRecInteger: integer;
SomeRecString: string;
end;
TMyClass = class(TObject)
private
FMyRec: TMyRec;
procedure SetSomeString(const AString: string);
public
property SomeInteger: integer read FMyRec.SomeRecInteger write FMyRec.SomeRecInteger;
property SomeString: string read FMyRec.SomeRecString write SetSomeString;
end;
procedure TMyClass.SetSomeRecString(const AString: string);
begin
If AString <> SomeString then
begin
// do something special if SomeRecString property is set
FMyRec.SomeRecString := AString;
end;
end;
Note:
The direct access to the record property SomeRecInteger
The use of SetSomeRecString to implement some special processing on this field only.
Hope this helps.
How about using a TObject instead of a Record?
type
TMyProperties = class(TObject)
SomeField: Integer;
end;
TMyObject = class(TObject)
private
FMyProperties: TMyProperties;
public
constructor Create;
destructor Destroy; override;
property MyProperties: TMyRecord read FMyProperties;
end;
implementation
constructor TMyObject.Create;
begin
FMyProperties := TMyProperties.Create;
end;
destructor TMyObject.Destroy;
begin
FMyProperties.Free;
end;
You can now read and set the properties of TMyProperties like this:
MyObject.MyProperties.SomeField := 1;
x := MyObject.MyProperties.SomeField;
Using this method, you don't need to individually get/set the values to/from the record. If you need to catch changes in FMyProperties, you can add a 'set' procedure in the property declaration.
Why not make the setter/getter part of the record?
TMyRecord = record
fFirstname: string;
procedure SetFirstName(AValue: String);
property
Firstname : string read fFirstname write SetFirstName;
end;
TMyClass = class(TObject)
MyRecord : TMyRecord;
end;
procedure TMyRecord.SetFirstName(AValue: String);
begin
// do extra checking here
fFirstname := AValue;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyClass: TMyClass;
begin
MyClass := TMyClass.Create;
try
MyClass.MyRecord.Firstname := 'John';
showmessage(MyClass.MyRecord.Firstname);
finally
MyClass.Free;
end;
end;
You're passing the record by value, so a copy of the record is stored by the object. From that point on there are effectively two objects; the original one and the copy held by the object. Changing one won't change the other.
You need to pass the record by reference.
type
TMyRecord = record
SomeField: Integer;
end;
PMyRecord = ^TMyRecord;
TMyObject = class(TObject)
private
FSomeRecord: PMyRecord;
procedure SetSomeRecord(const Value: PMyRecord);
public
property SomeRecord: PMyRecord read FSomeRecord write SetSomeRecord;
end;
this is an alternative to #SourceMaid's answer.
You could use a record (not a pointer to a record) inside your object and have a read only property which returns a pointer to your record.
the class:
type
TMyRecord = record
I:Integer;
end;
PMyRecord = ^TMyRecord;
TMyObject = class
private
FMyRecord:TMyRecord;
function GetMyRecordPointer: PMyRecord;
public
property MyRecord: PMyRecord read GetMyRecordPointer;
end;
the getter:
function TMyObject.GetMyRecordPointer: PMyRecord;
begin
result := #FMyRecord;
end;
usage:
o := TMyObject.Create;
o.MyRecord.I := 42;
ShowMessage(o.MyRecord.I.ToString);
o.MyRecord.I := 23;
ShowMessage(o.MyRecord.I.ToString);
o.Free;
you dont need a setter because you get a reference and work with. that mean that you cannot change the entire record by assigning a new one.
but you can manipulate the elements of the record directly without getting the error "Left side cannot be assigned to".

Instance reference in Delphi?

What's the Delphi equivalent of 'this' in C++? Could you please give some examples of its use?
In delphi Self is the equivalent of this. It is also assignable as described in here.
In most cases, you should not use self in the methods.
In fact, it's like if there was an implicit self. prefix when you access the class properties and methods, within a class method:
type
TMyClass = class
public
Value: string;
procedure MyMethod;
procedure AddToList(List: TStrings);
end;
procedure TMyClass.MyMethod;
begin
Value := 'abc';
assert(self.Value='abc'); // same as assert(Value=10)
end;
The self is to be used when you want to specify the current object to another method or object.
For instance:
procedure TMyClass.AddToList(List: TStrings);
var i: integer;
begin
List.AddObject(Value,self);
// check that the List[] only was populated via this method and this object
for i := 0 to List.Count-1 do
begin
assert(List[i]=Value);
assert(List.Objects[i]=self);
end;
end;
this above code will add an item to the TStrings list, with List.Objects[] pointing to the TMyClass instance. And it will check this has been the case for all items of the List.

Structure Delphi Generics Class

I'm new to generics and need some help to structure a class and implement methods.
I'm trying to use generics to serialize any TObject-JSON. Moreover, I want to be able to reuse the code.
These are my questions:
How do I create a generic constructor? I want to be able to use Self or Default(T), but it returns just nil.
V := Marshal.Marshal(ReturnObject) - This method requires a TObject, but I do not know how to reference the current object that was passed in.
How can I use this inside a method? Look at the code snipped below, marked with "Question 3".
This is my code:
TFileOperationResult = class(TObject)
private
FSuccess: Boolean;
//Error: PException;
FLastError: Integer;
function GetFailure: Boolean;
property Failure: Boolean read GetFailure;
public
property Success: Boolean read FSuccess write FSuccess;
property LastError: Integer read FLastError write FLastError;
end;
TResponseObject<T: class> = class(TObject)
private
FReturnObject: T;
function GetReturnObject: T;
function BaseStringsConverter(Data: TObject): TListOfStrings;
public
constructor Create; overload;
property ReturnObject: T read GetReturnObject;
procedure Serialize;
end;
constructor TResponseObject<T>.Create;
begin
// Question 1 - What should go in here?
end;
function TResponseObject<T>.GetReturnObject: T;
begin
Result := Default(T);// Is this correct?
end;
procedure TResponseObject<T>.Serialize;
var
Marshal: TJSONMarshal;
V: TJSONValue;
begin
Marshal := TJSONMarshal.Create(TJSONConverter.Create);
Marshal.RegisterConverter(TResponseObject<T>, BaseStringsConverter);
V := Marshal.Marshal(ReturnObject); // Question 2 - How Can I refer to 'Self'?
OutPut := V.ToString;
Marshal.Free;
end;
Calling code:
procedure TForm1.Test;
var
FileOperationResult: TResponseObject<TFileOperationResult>;
begin
FileOperationResult := TResponseObject<TFileOperationResult>.Create;
FileOperationResult.Serialize;
end;
Question 3:
procedure TForm1.MoveCopyFile<THowNowResponse>(ASource, DDestination: String);
var
FileOperationResult: TFileOperationResult;
begin
FileOperationResult := TFileOperationResult.Create;
// What to do?
end;
Any other comments are much appreciated.
It's hard to tell exactly what you're trying to do here, but I can make a guess. For TResponseObject, you want an object that can contain another object and operate on it. In that case, you probably want to pass it in to the constructor, like so:
constructor TResponseObject<T>.Create(value: T);
begin
FReturnObject := value;
end;
Likewise, if you make a GetReturnObject method, it should probably return the value of the FReturnObject field. (Or you could make the read accessor of the property just reference FReturnObject directly.)
function TResponseObject<T>.GetReturnObject: T;
begin
Result := FReturnObject;
end;
It's really hard to answer #3 since I don't know what you're trying to do with this, but hopefully my answers to the first two will help you get back on track. Just remember that generics don't have to be confusing; they're basically just type substitution. Anywhere you'd use a normal type in a non-generic routine, you can replace it with a <T> to create a generic routine, and then substitute any type that fits the constraints for that particular T.

Resources