In the following type:
MyClass = class(TInterfacedPersistent)
private
FMyProperty: Integer;
published
procedure setMyProperty(Value: Integer); virtual;
property MyProperty: Integer read FMyProperty write setMyProperty;
I would like to know the name of the setter method of the "MyProperty" property via RTTI. I've tried the following:
procedure ShowSetterMethodsNames(pMyObject: TObject);
var
vPropList: TPropList;
vCount, I: Integer;
begin
vCount:= GetPropList(pMyObject.ClassInfo, tkProperties, #vPropList);
for I:= 0 to vCount -1 do
begin
if Assigned(vPropList[I]^.SetProc) then
ShowMessage(pMyObject.ClassType.MethodName(vPropList[I]^.SetProc));
end;
end;
Although the pointer is not nil, all I have is an empty message. Does anybody have some tip to me?
P.S.: I'm using Delphi XE4, and I know I should use extended RTTI instead of classic, but anyway, I can't do what I want in both features... So, any help will be appreciated. Thanks for the replies.
FINAL EDITION, problem solved:
Here is the code working, based in the (help of my friends and...) RTTI unit (DoSetValue method of TRTTIInstanceProperty class):
procedure ShowVirtualSettersNames(pObject: Pointer);
var
vSetter, vPointer: Pointer;
vPropList: TArray<TRttiProperty>;
vProp: TRttiProperty;
begin
vPropList:= RTTIUtils.ExtractProperties(TObject(pObject).ClassType); // Helper to get properties from a type, based in extended RTTI
for vProp in vPropList do
begin
vPointer:= TRttiInstanceProperty(vProp).PropInfo^.SetProc;
vPointer:= PPointer(PInteger(pObject)^ + Smallint(vPointer))^;
ShowMessage(TObject(pObject).ClassType.MethodName(vPointer));
end;
end;
This ONLY WORKS FOR VIRTUAL SETTERS, for statics the message is empty. Thanks everyone!
You can retrieve this method name, if
a) move the method to the published section (classic RTTI works with this section only (more accurately - compiled with {$M+} directive))
b) use right class specifier - MyClass.MethodName, because MethodName is class function
This code works on D7 and XE3:
MyClass = class(TInterfacedPersistent)
private
FMyProperty: Integer;
published
procedure setMyProperty(Value: Integer);
property MyProperty: Integer read FMyProperty write setMyProperty;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ppi: PPropInfo;
begin
ppi := GetPropInfo(MyClass, 'MyProperty');
ShowMessage(MyClass.MethodName(ppi.SetProc));
end;
P.S. What Delphi version are you using? What about Extended RTTI (since D2010)?
Read c:\rad studio\9.0\source\rtl\common\System.Rtti.pas
procedure TRttiInstanceProperty.DoSetValue
The setter of the property may be
a field (variable)
a static procedure
a virtual procedure (your case)
And those cases make PropInfo^.SetProc have different semantics of its value.
Direct address only applies to static procedures. For virtual methods you add a VMT offset and take the code address from that memory cell, as specified in that code i mentioned (but would not quote for copyright reasons).
Or you just could use TRttiProperty.SetValue and let Delphi do all those little under the hood details. See http://docwiki.embarcadero.com/Libraries/XE2/en/System.Rtti.TRttiProperty.SetValue
EDIT:
the code removed - it did not worked verbatim and the topic starter provided working version.
Regarding and I know I should use Extended RTTI instead of classic one - that is questionable claim. Extended RTTI is known to work noticeably slower than classic one. Dunno if someone did profiled it, but i suspect that is mostly due to the slow code of TValue. You can google and find that lot of people complained of slow TValue implementation and provided alternative ones with fixed efficiency. However since Extended RTTI only uses stock TValue it cannot benefit from those implementations and remains slower than classic one.
Related
I'd like to understand the principles of adding methods to RTTI (I mean the old one, which is supported by old Delphi versions (before Delphi 2010) or by FPC). As far as I know the RTTI is supposed to have information about published methods. But the following example doesn't work in my case:
{$M+}
TMyClass = class
published
procedure testfn(a,b,c: Integer);
end;
{$M-}
...
procedure TMyClass.testfn(a,b,c: Integer);
begin
ShowMessage('s');
end;
...
GetPropInfo(TMyClass, 'testfn'); // returns nil
I'd like to understand what I need to change to receive PPropInfo for the method.
I want to get the PTypeInfo for the method. In case of a property it can be retrieved via
PropInfo := GetPropInfo(...);
TypeInfo := PropInfo^.PropType;
TypeData := GetTypeData(TypeInfo);
I need something like that for methods.
Have a look at the mORMot Framework. It includes a whole bunch of additional RTTI helper functions including the very handy TMethodInfo object along with this handy function to populate it.
/// retrieve a method RTTI information for a specific class
function InternalMethodInfo(aClassType: TClass; const aMethodName: ShortString): PMethodInfo;
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to cast a Interface to a Object in Delphi
Using Delphi 5; I have an interface that I cannot change for legacy reasons. I am passing (pointers to) that interface all over the place. The implementing class has several new properties - is there a way to force a cast from the interface to the actual implementation?
http://www.malcolmgroves.com/blog/?p=500 says that this is (newly) implemented in Delphi 2010, which strongly suggest that it wasn't possible before. Is this indeed the case, or is there a way I'm not familiar with? RTTI, maybe?
(I checked, and if pScore is TOleScore then is indeed not allowed by the Delphi 5 compiler - here pScore is my pScore: IScore argument, and TOleScore is the implementing class.)
The classic take on this is by Hallvard Vassbotn: Hack #7: Interface to Object
More recently Barry Kelly, a Delphi compiler engineer, also provided an implementation: An ugly alternative to interface to object casting
I think both approaches should work.
Incidentally, does anyone know if Hallvard is still active? I've not come across him in the past few years.
H/t to my boss, the answer is: use the incredibly useful JEDI library, specifically the GetImplementorOfInterface method.
i do what the "possible duplicate" question's accepted answer does:
Have the object implement the IObject interface:
IObject = interface(IUnknown)
['{39B4F98D-5CAC-42C5-AF8D-0237C8EFBE4C}']
function GetSelf: TObject;
end;
So it would be:
var
thingy: IThingy;
o: TOriginalThingy;
begin
o := (thingy as IObject).GetSelf as TOriginalThingy;
Update: To drive the point home, you can add a new interface to an existing object.
Existing object:
type
TOriginalThingy = class(TInterfacedObject, IThingy)
public
//IThingy
procedure DrinkCokeZero; safecall;
procedure ExcreteCokeZero; cafecall;
end;
Add IObject as one of the interfaces it exposes:
type
TOriginalThingy = class(TInterfacedObject, IThingy, IObject)
public
//IThingy
procedure DrinkCokeZero; safecall;
procedure ExcreteCokeZero; cafecall;
//IObject - provides a sneaky way to get the object implementing the interface
function GetSelf: TObject;
end;
function TOriginalThingy.GetSelf: TObject;
begin
Result := Self;
end;
Typical usage:
procedure DiddleMyThingy(Thingy: IThingy);
var
o: TThingy;
begin
o := (Thingy as IObject).GetSelf as TThingy;
o.Diddle;
end;
Please forgive the verbosity of the following code example. Using Delphi 2009, I created the two classes TOtherClass and TMyClass:
TOtherClass = class(TObject)
public
FData: string;
end;
TMyClass = class(TObject)
private
FIndxPropList: Array of TOtherClass;
function GetIndxProp(Index: Integer): TOtherClass;
procedure SetIndxProp(Index: Integer; Value: TOtherClass);
public
property IndxProp[Index: Integer]: TOtherClass read GetIndxProp write SetIndxProp;
end;
with access specifiers implemented as
function TMyClass.GetIndxProp(Index: Integer): TOtherClass;
begin
Result := self.FIndxPropList[Index];
end;
procedure TMyClass.SetIndxProp(Index: Integer; Value: TOtherClass);
begin
SetLength(self.FIndxPropList, Length(self.FIndxPropList) + 1);
self.FIndxPropList[Length(self.FIndxPropList) - 1] := Value;
end;
It's use can be illustrated as follows:
procedure Test();
var
MyClass: TMyClass;
begin
MyClass := TMyClass.Create;
MyClass.IndxProp[0] := TOtherClass.Create;
MyClass.IndxProp[0].FData := 'First instance.';
MyClass.IndxProp[1] := TOtherClass.Create;
MyClass.IndxProp[1].FData := 'Second instance.';
MessageDlg(MyClass.IndxProp[0].FData, mtInformation, [mbOk], 0);
MessageDlg(MyClass.IndxProp[1].FData, mtInformation, [mbOk], 0);
MyClass.IndxProp[0].Free;
MyClass.IndxProp[1].Free;
MyClass.Free;
end;
Never mind the obvious flaws of this "design". I realized that I'd like to be able to access the property IndxProp via RTTI, and subsequently moved the IndxProp to the published section. Much to my disappointment, I found that indexed properties are not allowed in the published section. As far as I understand (see Barry Kellys comment at How do I access Delphi Array Properties using RTTI), moving to D2010 won't enable me to do this.
On the other hand, the following is a quote from Robert Loves blog: "... properties and methods are now available via RTTI in both public and published sections, and Fields are available in all of the sections." (My italics.)
My question is this: if it's true that it is possible to get RTTI for public fields in D2010, shouldn't my original example (as shown above) work in D2010 (with RTTI)? Thanks in advance!
Yes, if all the property reader does is index into an array field or list-class field, then you can use RTTI to index into the field directly. This is kind of fragile, though, since it breaks your encapsulation, requiring you to write code to a specific implementation detail instead of a general principle, which is what RTTI is mainly good for. Your RTTI code has to match the exact structure of your class, and if it changes you have to change the code as well. That sort of defeats the purpose of using RTTI.
But, if there's no alternative available, since array properties have no RTTI for them, it may be the only way, for now at least.
EDIT: Updating this answer. Support for indexed properties was added to the extended RTTI system in XE2. (However, due to unrelated stability issues, you might want to wait for XE3...)
I want to add a published property into TWinControl.
Is there someway to do this without the necessity of recompiling the base source code ?
If not, some way to recompile the base source code without too much troubles ?
Tks in advice...
EDIT 'CAUSE OF NEW IDEAS
Alright, What I'm thinking to do I'm trying to override the _GetMem from System.pas for classes
inherited from TWinControl.
Why ? 'Cause I'll alloc some extra space to the objects enough to an integer.
Why an integer ? 'Cause this way I can add any pointer to object.
So on the helper class to TWinControl I can make a Get an Set function to access this space of memory.
Good isn't it ? How to do this ?
Overrideing the GetMem procedure I can use the same strategy used on FastCode, create a jumper to the new procedure.
What I need now is understand how this memory alloc works InstanceSize to override this.
At all I'm studding how do Delphi do this... And to add this on DFM I will do the same way, I'll create a jumper to the filer.
Someone have some idea to add the new space in objects ? What method I need to override ? The jumper I know how to do.
Tks Again.
EDIT = Evolution
I think that I did the injection of memory.
I need to do more tests.
I've just did it, I'm not caring about optimizations at the moment, if some one would like to test it, here goes the code.
Just add the unit as the first unit of your project.
unit uMemInjection;
interface
uses
Controls;
type
THelperWinControl = class Helper for TWinControl
private
function RfInstanceSize: Longint;
function GetInteger: Integer;
procedure SetInteger(const Value: Integer);
public
property RfInteger: Integer read GetInteger write SetInteger;
end;
implementation
uses
Windows;
procedure SInstanceSize;
asm
call TWinControl.InstanceSize
end;
function THelperWinControl.GetInteger: Integer;
begin
Result := Integer(PInteger(Integer(Self) + (Self.InstanceSize - SizeOf(Integer)))^);
end;
function THelperWinControl.RfInstanceSize: Longint;
begin
Result := PInteger(Integer(Self) + vmtInstanceSize)^;
Result := Result + SizeOf(Integer);
end;
/////////////////////////////////////////////// FastCode ///////////////////////////////////////////////
type
PJump = ^TJump;
TJump = packed record
OpCode: Byte;
Distance: Pointer;
end;
function FastcodeGetAddress(AStub: Pointer): Pointer;
begin
if PBYTE(AStub)^ = $E8 then
begin
Inc(Integer(AStub));
Result := Pointer(Integer(AStub) + SizeOf(Pointer) + PInteger(AStub)^);
end
else
Result := nil;
end;
procedure FastcodeAddressPatch(const ASource, ADestination: Pointer);
const
Size = SizeOf(TJump);
var
NewJump: PJump;
OldProtect: Cardinal;
begin
if VirtualProtect(ASource, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
begin
NewJump := PJump(ASource);
NewJump.OpCode := $E9;
NewJump.Distance := Pointer(Integer(ADestination) - Integer(ASource) - 5);
FlushInstructionCache(GetCurrentProcess, ASource, SizeOf(TJump));
VirtualProtect(ASource, Size, OldProtect, #OldProtect);
end;
end;
/////////////////////////////////////////////// FastCode ///////////////////////////////////////////////
{ THelperWinControl }
procedure THelperWinControl.SetInteger(const Value: Integer);
begin
PInteger(Integer(Self) + (Self.InstanceSize - SizeOf(Integer)))^ := Value;
end;
initialization
FastcodeAddressPatch(FastcodeGetAddress(#SInstanceSize), #TWinControl.RfInstanceSize);
end.
Thanks to Smasher, I remembered how the Delphi team used class helpers and a designer trick to add properties to Delphi 2007 without breaking binary compatibility with Delphi 2006.
See this great article by Hallvard Vassbotn on how to do this.
I think it solves most, if not all, of your problems.
look for these things in the article:
TCustomFormHelper = class helper for TCustomForm
The FPixelsPerInch storage hack
Injecting design-time properties
Defining the streaming properties
You'll have to work your own way to do the streaming, though, as you hook from the outside world into TWinControl, but that might be possible too.
--jeroen
Delphi2007 and higher have "class helpers".
You can introduce new functions and properties, but no fields/variables. So you have to store the value of you new property in a extra object (via factory or whatever) or (very ugly) in the .Tag property...
Don't know if class helper also work in packages/design time?
If you are using this property only on the application level, you may use the following approaches:
composition: bundle a reference to TWinControl object with other properties into new class, and pass/operate objects this class in your calls
dictionary-like functions: GetMyPropertyFor( AWinControl: TWinControl): and SetMyPropertyFor( AWinControl: TWinControl: AValue: ), which internally maintain additional property for each called TWinControl object
ADDITION: Based on your additional comment, existing Tag property should play well for your needs. You can even define 'levels' by using different values there.
No, there is no way to modify TWinControl without recompiling the VCL. Also I don't recommend changing the VCL (since having a "custom" VCL can impact the portability of your project - at the very least between Delphi installations). I would aim at making another class that inherit from TWinControl and then add your published property to this new class.
If you still want to change the VCL see the following post:
http://www.delphigroups.info/2/6/744173.html
Note that "you will no longer be able to compile using runtime
packages"...
(I know the answer is a bit dense, comment on it what details you need more info about)
What you could do is what for instance TGridPanel does: it adds the Column, Row, ColumnSpan and RowSpan 'properties' to the object inspector for all components that are on the GridPanel.
That will solve your design-time support.
I thought I had a reference on how the TGridPanel does this (and TFlowPanel does similar things), but I can't find it right now. Probably Ray Konopka explained this during a conference, but that info might not be on-line.
For run-time support, you could go with class helpers.
When using class helpers, note that only the nearest visible one for a class will apply.
Another route you might follow is to use the Tag property (which is an Integer, but you can cast it to a Pointer or a TObject), but you might be bitten by others using that too.
You'd have to create your own design-time support for those tag properties though.
--jeroen
I've got a lot of older code that uses the old-style pascal object type that I'm trying to get working in Delphi 2009. It compiles, but there seems to be several problems dealing with virtual methods. It appears that this problem has already been reports on Quality Central:
http://qc.embarcadero.com/wc/qcmain.aspx?d=71723
I was hoping anyone who still uses these (PatrickvL maybe?) could respond with more information. We've got A LOT of code that uses objects and if this isn't going to get fixed, we're stuck. Thanks in advance!
If you're using virtual methods, then you're clearly accessing the objects by reference, not by value. That's how classes always work in Delphi, so switching to classes shouldn't be too hard.
For any object types that don't have virtual methods, you should be able to turn them into records. Records are allowed to have methods now, as well as visibility specifiers. The don't support inheritance, though.
Old-style objects have been deprecated since February 14, 1994, the release date of the first version of Delphi. They've been deteriorating ever since. You should have moved away from them years ago.
I must admit I had a couple of beers looking at this, just for the challenge :) You need some magic bytes. According to legend Old style objects ONLY create a space for the pointers if you use ANY virtual methods. No Virtual methods NO VMT.
The VMT pointer is ALWAYS FIRST with new style objects because they all declare virtual methods. Seems Someone forgot that with old style objects the VMT can come later. so assuming its a just one pointer this makes it work on my D2009. I'm not into the guts of the compiler, a guy called Dave Jewell who used to write for PC pro could possibly confirm that this will be stable...
Type
PObject1 = ^TObject1;
TObject1 = Object
Magic: Array[0..3] of Byte; //or integer or whatever I was playing with the size
FCount : Integer;
Constructor Init;
Procedure Add; virtual;
Procedure Deduct; virtual;
end;
Type
PObject2 = ^TObject2;
TObject2 = Object(TObject1)
Constructor Init;
end;
Then after construction these work:
.
.
.
Object2^.Add;
Object2^.Deduct;
and I get the appropriate console output
I added an additional proc just to make sure that it worked for 2 virtuals :)
Incidentally they work whether you put the ^ or not 2009 knows what you mean :(
Lacking a proper fix from embracodeland You still may still have to alter each BASE object definition. Hopefully you could do it with find and insert/replace or Grep... Good luck.
Ok - Done that - I cannot get it to fail.... Is your D2009 Fully Patched?
Project/Compiler Options?
For absolute certainty and comparison here are my units:
---------------Project File
program testD2009;
{$APPTYPE CONSOLE}
uses
SysUtils,
Object1U in 'Object1U.pas',
Object2U in 'Object2U.pas';
Var
Object1 : PObject1;
Object2 : PObject2;
begin
try
Object1 := New(PObject1,Init);
Object1^.Add;
Object1^.Deduct;
Object2 := New(PObject2,Init);
Object2^.Add;
Object2^.Deduct;
readln;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
--------------Object1 unit
unit Object1U;
interface
uses SysUtils;
Type
PObject1 = ^TObject1;
TObject1 = Object
Magic: Array[0..3] of Byte;
FCount : Integer;
Constructor Init;
Procedure Add; virtual; { removing virtual allows the program to run }
Procedure Deduct; virtual; { removing virtual allows the program to run }
end;
implementation
Procedure TObject1.Add;
begin
Writeln('Object1 Add');
end;
procedure TObject1.Deduct;
begin
Writeln('Object1 Deduct');
end;
Constructor TObject1.Init;
begin
inherited;
FCount := 0;
Writeln('TObject1 Init');
end;
end.
----------------Object 2 unit
unit Object2U;
interface
uses Object1U;
Type
PObject2 = ^TObject2;
TObject2 = Object(TObject1)
Constructor Init;
Procedure Add; virtual; { removing virtual allows the program to run }
Procedure Deduct; virtual; { removing virtual allows the program to run }
end;
implementation
procedure TObject2.Add;
begin
Writeln('Object2 Add');
inherited;
end;
procedure TObject2.Deduct;
begin
Writeln('Object2 Deduct');
inherited;
end;
Constructor TObject2.Init;
begin
Inherited Init;
fCount := 1;
Writeln('TObject2:Init');
end;
end.
----------------Program Output:
TObject1 Init
Object1 Add
Object1 Deduct
TObject1 Init
TObject2:Init
Object2 Add
Object1 Add
Object2 Deduct
Object1 Deduct
Puzzled I am :).
I sent an e-mail to our local representatives from Embarcadero in regards to this problem and referred them to the report on Quality Central. They basically told us to move all objects to classes, so I'm guessing they're not planning on fixing this...ever. I think we've pretty much accepted that this is the way we have to go if we want to move forward, so now we just have to schedule that work before we can proceed with our upgrade to Delphi 2009.
Just wanted to thank everyone who tried to help, but I believe at this point it's a lost cause :-(