John Kouraklis 94 Posted February 11, 2019 Hi, I want to write a function and allow the user of the function to pass a record that I do not know in advance. Then, the idea is I iterate through the fields in the function. Something like this: function pass (aRec: record): boolean; But a 'record' can't be used like this. I've thought of two approaches but without being able to make them work: 1. Use of pointers: function pass (aRP: Pointer): boolean; begin .... /// here I do not know how to (cast the pointer to any record and) iterate using RTTI /// I think this can not be done as the cast seems very arbitrary ... end; 2. A kind of adapter record: type TAdapterRec<T> = record instance: T constructor Create (aRec: T); end; and then tried to declare the function: function pass (aRec: TAdapterRec<T>): boolean; but generics like these are not allowed. Anyone can help with this? I would like to avoid using classes. Thanks Share this post Link to post
Kryvich 165 Posted February 11, 2019 Try overloaded functions function pass(aRec: TMyRecord1): Boolean; overload; function pass(aRec: TMyRecord2): Boolean; overload; ... Share this post Link to post
dummzeuch 1505 Posted February 11, 2019 function pass(var aRec): boolean; or function pass(const aRec): boolean; Share this post Link to post
Uwe Raabe 2057 Posted February 11, 2019 Make the function a static class function of a record: type TMyPassHandler = record public class function Pass<T:record>(const Value: T): Boolean; static; end; class function TMyPassHandler.Pass<T>(const Value: T): Boolean; begin Result := False; { do whatever is needed } end; Most of the time the compiler should be able to infer the proper type, so a call would look like: TMyPassHandler.Pass(myRec); 2 Share this post Link to post
David Heffernan 2345 Posted February 11, 2019 2 hours ago, dummzeuch said: function pass(var aRec): boolean; or function pass(const aRec): boolean; How will you use RTTI with this? Share this post Link to post
David Heffernan 2345 Posted February 11, 2019 2 hours ago, Uwe Raabe said: Make the function a static class function of a record: type TMyPassHandler = record public class function Pass<T:record>(const Value: T): Boolean; static; end; class function TMyPassHandler.Pass<T>(const Value: T): Boolean; begin Result := False; { do whatever is needed } end; Most of the time the compiler should be able to infer the proper type, so a call would look like: TMyPassHandler.Pass(myRec); If the goal is to use RTTI then it seems questionable to use compile time generics. Will result in generic bloat. May as well pass the address of the record and the type info. Or if you want to ensure type safety have the generic method call a further helper method that is non-generic and receives the address of the record and the type info. That avoids the bloat and gives benefit of type safety. Share this post Link to post
John Kouraklis 94 Posted February 11, 2019 (edited) The goal is to extract the fields and their values from the record. The class function approach looks more promising but it looks such "openness" makes things complicated. I think I will pass the list of the fields directly rather than the record itself... Thanks everyone Edited February 11, 2019 by John Kouraklis Share this post Link to post
John Kouraklis 94 Posted February 11, 2019 4 hours ago, dummzeuch said: function pass(var aRec): boolean; or function pass(const aRec): boolean; 1 hour ago, David Heffernan said: How will you use RTTI with this? I would be great if this were possible... Share this post Link to post
David Heffernan 2345 Posted February 11, 2019 1 hour ago, John Kouraklis said: I would be great if this were possible... It's not possible. How could it be? Share this post Link to post
Mahdi Safsafi 225 Posted February 11, 2019 1 hour ago, John Kouraklis said: I would be great if this were possible... What about that : type TRecord1 = record private var FTypeInfo: Pointer; // this must be the first field and all record must implement it. public var FField1: Integer; // Record fields. class function Create: TRecord1; static; end; TRecord2 = record private var FTypeInfo: Pointer; public var FField1: Integer; FField2: string; class function Create: TRecord2; static; end; procedure DoSomething(const Rec); type TRecordHack = record FTypeInfo: Pointer; // since FTypeInfo is the first field in all records, we can hack it ! end; PRecordHack = ^TRecordHack; var PHack: PRecordHack; TypeInfo: Pointer; LCntx: TRttiContext; LType: TRttiType; begin PHack := PRecordHack(@Rec); TypeInfo := PHack^.FTypeInfo; // RTTI: LCntx := TRttiContext.Create(); try LType := LCntx.GetType(TypeInfo); ShowMessage(LType.Name); finally LCntx.Free(); end; end; procedure TForm1.Button1Click(Sender: TObject); var a: TRecord1; b: TRecord2; begin a := TRecord1.Create(); DoSomething(a); b := TRecord2.Create(); DoSomething(b); end; { TRecord1 } class function TRecord1.Create: TRecord1; begin Result.FTypeInfo := TypeInfo(TRecord1); end; { TRecord2 } class function TRecord2.Create: TRecord2; begin Result.FTypeInfo := TypeInfo(TRecord2); end; 2 Share this post Link to post
John Kouraklis 94 Posted February 11, 2019 @Mahdi Safsafi Very cool. Thanks for this A couple of questions: 1. Do you need to create/Free TRTTIContext? All the books/articles I've seen they just use the record 2. The TypeInfo variable in DoSomething conflicts with TypeInfo from RTTI. It should use a different name, right? 3. How do I get the value of the fields now? I tried this: for lFields in LType.GetFields do Writeln(' '+lFields.Name + ': '+lFields.GetValue(TypeInfo(Rec)).AsString); but the instance in GetValue is not correct. Thanks again Share this post Link to post
dummzeuch 1505 Posted February 11, 2019 6 hours ago, David Heffernan said: How will you use RTTI with this? I missed the part about RTTI in the question. Share this post Link to post
Mahdi Safsafi 225 Posted February 11, 2019 Quote 1. Do you need to create/Free TRTTIContext? All the books/articles I've seen they just use the record I saw many people using TRTTIContext without freeing it. That's wrong ! The Free function wasn't there for nothing. I just check it and found that it assigns a nil to interface ! procedure TRttiContext.Free; begin FContextToken := nil; end; You should always use Free when a record implements it even if it does nothing !!! It might do nothing currently ... but maybe the developer introduce it to use it later or it does something on other configuration (ARM ?)! As an example, from the Win32 api FlushInstructionCache function does nothing on x86 but it's important on ARM ! Quote 2. The TypeInfo variable in DoSomething conflicts with TypeInfo from RTTI. It should use a different name, right? My bad ... Sorry. Quote 3. How do I get the value of the fields now? I tried this: You're missing "@" ! Writeln(' '+lFields.Name + ': '+lFields.GetValue(TypeInfo(@Rec)).AsString); I just played a little with the above code : uses System.SysUtils, System.Rtti, System.TypInfo, System.Generics.Collections; type TRecord1 = record private var FTypeInfo: Pointer; // this must be the first field and all record must implement it. public var FField1: Integer; // Record fields. class function Create: TRecord1; static; end; TRecord2 = record private var FTypeInfo: Pointer; public var FField1: Integer; FField2: string; FField3: Boolean; class function Create: TRecord2; static; end; procedure DoSomething(const Rec); type TRecordHack = record FTypeInfo: Pointer; // since FTypeInfo is the first field in all records, we can hack it ! end; PRecordHack = ^TRecordHack; var PHack: PRecordHack; LTypeInfo: Pointer; LCntx: TRttiContext; LType: TRttiType; LField: TRttiField; LValue: TValue; LKind: TTypeKind; SValue: string; begin PHack := PRecordHack(@Rec); LTypeInfo := PHack^.FTypeInfo; // RTTI: LCntx := TRttiContext.Create(); try LType := LCntx.GetType(LTypeInfo); Writeln(Format('record (%s):', [LType.Name])); for LField in LType.GetFields do begin if (LField.Visibility = mvPrivate) then // skip private (FTypeInfo). Continue; LValue := LField.GetValue(@Rec); LKind := LValue.Kind; case LKind of tkInteger: SValue := IntToStr(LValue.AsInteger); tkString, tkUString: SValue := LValue.AsString; tkEnumeration: SValue := GetEnumName(LValue.TypeInfo, TValueData(LValue).FAsSLong); else SValue := '??'; end; Writeln(Format(' field (%s) = %s', [LField.Name, SValue])); end; finally LCntx.Free(); end; end; { TRecord1 } class function TRecord1.Create: TRecord1; begin Result.FTypeInfo := TypeInfo(TRecord1); end; { TRecord2 } class function TRecord2.Create: TRecord2; begin Result.FTypeInfo := TypeInfo(TRecord2); end; var a: TRecord1; b: TRecord2; begin a := TRecord1.Create(); a.FField1 := 1; DoSomething(a); b := TRecord2.Create(); b.FField1 := -10; b.FField2 := 'foo'; DoSomething(b); Readln; end. 1 Share this post Link to post
Attila Kovacs 629 Posted February 11, 2019 6 minutes ago, Mahdi Safsafi said: I saw many people using TRTTIContext without freeing it. That's wrong ! Like me. It's "created" once at startup to generate the rtti cache and stored in a global variable and never "created" again or "free"'d. Single threaded app. Share this post Link to post
Mahdi Safsafi 225 Posted February 11, 2019 22 minutes ago, Attila Kovacs said: Like me. It's "created" once at startup to generate the rtti cache and stored in a global variable and never "created" again or "free"'d. Single threaded app. You read my explanation. From now, you' ve no excuses to use it without freeing it 😉 I'm watching you. Share this post Link to post
David Heffernan 2345 Posted February 11, 2019 48 minutes ago, Mahdi Safsafi said: You read my explanation. From now, you' ve no excuses to use it without freeing it 😉 I'm watching you. You are quite wrong. No point freeing it. No point even creating it. 1 Share this post Link to post
Mahdi Safsafi 225 Posted February 11, 2019 32 minutes ago, David Heffernan said: You are quite wrong. No point freeing it. No point even creating it. I believe that you didn't understand me clearly. My answer wasn't about whether is pointless or not ! my answer was about the coding style (and I gave an example about the Win32 api). Suppose I made a library that have a record like this : type TDummy = record // ... public function Create(): TDummy; procedure Free(); end; { TDummy } function TDummy.Create: TDummy; begin // FOR NOW ... do nothing here ! end; procedure TDummy.Free; begin // on this platform do nothing for now ! // feature platform do something. end; Currently Create and Free do nothing on Win32(x86). Yep there is no need to use them. But I use them just for future compatibility. Now after 1 year, I decided to port my library to let say WinRT or MIPS. Now my Free must do something here (really)! Quote procedure TDummy.Free; begin {$IF DEFINED(MIPS) || DEFINED(WINRT)} DoSeriousJob(); {$ENDIF} end; Now, from your approach, you must track all variables and free them if you want your code to run successfully on WinRT, MIPS. For me, because I used them on all configurations, I wan't going to track them ! Also, is there any guaranties that "FContextToken" won't change to something else on future ? Share this post Link to post
Attila Kovacs 629 Posted February 11, 2019 TRttiContext.Free does already something. It kills the RTTI cache which I'm trying to avoid. But go ahead and create/free it every single time if you wish. Share this post Link to post
David Heffernan 2345 Posted February 11, 2019 54 minutes ago, Mahdi Safsafi said: I believe that you didn't understand me clearly. My answer wasn't about whether is pointless or not ! my answer was about the coding style (and I gave an example about the Win32 api). Suppose I made a library that have a record like this : type TDummy = record // ... public function Create(): TDummy; procedure Free(); end; { TDummy } function TDummy.Create: TDummy; begin // FOR NOW ... do nothing here ! end; procedure TDummy.Free; begin // on this platform do nothing for now ! // feature platform do something. end; Currently Create and Free do nothing on Win32(x86). Yep there is no need to use them. But I use them just for future compatibility. Now after 1 year, I decided to port my library to let say WinRT or MIPS. Now my Free must do something here (really)! Now, from your approach, you must track all variables and free them if you want your code to run successfully on WinRT, MIPS. For me, because I used them on all configurations, I wan't going to track them ! Also, is there any guaranties that "FContextToken" won't change to something else on future ? No point planning for a future you can't see. The real problem was the stupid design in adding Create and Free methods that serve no purpose and confusing so many people. Best strategy is to use a singleton for the context. Share this post Link to post
John Kouraklis 94 Posted February 11, 2019 @Mahdi Safsafi Thanks! the hack helps me a lot. About the debate regarding Create/Free, it looks to me it is good practice. Similar to this, I've seen people always but always setting interfaces to nil as a matter of coding style Share this post Link to post
Leif Uneus 43 Posted February 12, 2019 There was a race condition in the TRttiContext, causing errors in multi-thread applications. As @David Heffernan says, it is best used as a singleton. Seems to be fixed in Delphi 10.3 Rio. https://quality.embarcadero.com/browse/RSP-9815 https://stackoverflow.com/q/27368556/576719 "TRttiContext Multi-thread issue" 1 Share this post Link to post
Clément 148 Posted February 12, 2019 Hi, I wrote this code using XE. It might help you. type TMyRecord = Record item1: string; item2: Integer; Item3: Currency End; TForm68 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } function GetProperties( aTypeInfo : PTypeInfo; var aRec ) : String; end; implementation {$R *.dfm} procedure TForm68.FormCreate(Sender: TObject); var R : TMyRecord; begin Caption := GetProperties( TypeInfo(TMyRecord), R ); end; function TForm68.GetProperties(aTypeInfo: PTypeInfo; var aRec): String; var rtc : TRttiContext; lTyp: TRttiType; lFld : TRttiField; begin rtc := TRttiContext.Create; lTyp := rtc.GetType(aTypeInfo); for lFld in lTyp.GetFields do Result := Result + ' '+ lFld.Name; end; As you can see, all you need is Typeinfo. You can pass aRec as pointer if you need an instance to assign values. The result is obviously: Item1 item2 item3 Hope this helps, Share this post Link to post
Rudy Velthuis 91 Posted February 13, 2019 (edited) On 2/11/2019 at 12:18 PM, David Heffernan said: If the goal is to use RTTI then it seems questionable to use compile time generics. Will result in generic bloat. Not much, as this is only one function. And it is more elegant than passing a pointer and type info. I would then make this function call a private function with the pointer obtained from the parameter and the typeinfo obtained from T. That way, there will hardly be any bloat. And if this function is not critical, you will never notice a difference. Edited February 13, 2019 by Rudy Velthuis Share this post Link to post