Andre1 0 Posted June 22, 2022 Hi, I have written a wrapper library to make use of Firemonkey from the programming language D. A simple example looks like this: import delta.core; import System.Classes, System.UITypes, FMX.Forms, FMX.Types, FMX.Memo; void main() { deltaLibrary.load(`.\views\Win32\Debug\Project1.dll`); Application.Initialize; Application.MainForm = TCustomForm.CreateNew(Application); Application.MainForm.Caption = "Sample"; auto memo = TMemo.Create(Application.MainForm); memo.Lines.Add("Hello World!"); memo.Align = TAlignLayout.Client; memo.Parent = Application.MainForm; Application.MainForm.Show(); Application.Run(); } The Project1.dll is written in Delphi and provides generic functions for dynamically creating Delphi class instances and calling their methods. While this is working fine, I have issues calling method which have record arguments. Example TCanvas.DrawLine has two time the record type TPointF: procedure DrawLine(const APt1, APt2: TPointF; const AOpacity: Single); overload; Within my DLL I have a generic function to call instance methods which have exactly this kind of arguments (record, record, single) procedure executeInstanceMethodReturnNoneArgsStructStructFloat(const Reference: Integer; const AName: PAnsiChar; Reference2, Reference3: Integer; s: Single); stdcall; begin // What needs to be done here executeInstanceMethod(Reference, string(AName), [???]); end; function executeInstanceMethod(const Reference: Integer; const AName: string; const Args: array of TValue): TValue; var context: TRttiContext; instType: TRttiInstanceType; obj: TObject; begin context := TRttiContext.Create; try try obj := TObject(Reference); instType := (context.GetType(obj.ClassType) as TRttiInstanceType); result := instType.GetMethod(AName).Invoke(obj, Args); except on E: Exception do writeln(E.ClassName + ' error raised, with message : ' + E.Message); end; finally context.Free; end; end; (Argument Reference has the class reference to e.g. TCanvas, AName has the method name e.g. DrawLine. After that the actual methods of DrawLine are following). From my application written in D I want to call executeInstanceMethodReturnNoneArgsStructStructFloat and pass references to records (D structs binary compatible with the Delphi records). Within the Delphi procedure executeInstanceMethodReturnNoneArgsStructStructFloat these record references should be converted into something which can be used by TRttiMethod.Invoke. Is this technically possible and can you give me a hint or an example how the coding in executeInstanceMethodReturnNoneArgsStructStructFloat should look like? Kind regards André Share this post Link to post
Fr0sT.Brutal 900 Posted June 23, 2022 When record argument is declared as const, Delphi always passes it by reference (pointer). So you should try to feed the function with pointers. BTW, if you use Reference as pointer, don't declare it as Integer. Share this post Link to post
Andre1 0 Posted June 23, 2022 Thanks I tried your suggestion but it didnt' worked. An invalid type conversion is show. It seems just passing untyped pointer of the record is not possible in the invoke method. I simplified the problem by creating the example just as Delphi console application: program foo; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Classes, System.rtti, System.TypInfo; type TDateRec = record Year: Integer; end; TExample = class public function Calc(const dateRec: TDateRec): Integer; end; function TExample.Calc(const dateRec: TDateRec): Integer; begin result := dateRec.Year; end; var ex: TExample; dateRec: TDateRec; intResult: Integer; function executeInstanceMethod(const Reference: Integer; const AName: string; const Args: array of TValue): TValue; var context: TRttiContext; instType: TRttiInstanceType; obj: TObject; begin context := TRttiContext.Create; try try obj := TObject(Reference); instType := (context.GetType(obj.ClassType) as TRttiInstanceType); result := instType.GetMethod(AName).Invoke(obj, Args); except on E: Exception do writeln(E.ClassName + ' error raised, with message : ' + E.Message); end; finally context.Free; end; end; function executeInstanceMethodReturnIntArgsStruct(const Reference: Integer; const AName: PAnsiChar; Reference2: Pointer): Integer; begin // Just passing the untyped pointer does not work ? result := executeInstanceMethod(Reference, string(AName), [Reference2]) .AsInteger; end; begin try ex := TExample.Create; dateRec.Year := 2022; executeInstanceMethodReturnIntArgsStruct(Integer(ex), 'Calc', @dateRec); except on E: Exception do writeln(E.ClassName, ': ', E.Message); end; end. Kind regards André Share this post Link to post
Fr0sT.Brutal 900 Posted June 24, 2022 Where the error occurs and what it is? I repeat: don't cast pointers to Integer! It's incorrect on x64 Share this post Link to post
Andre1 0 Posted June 24, 2022 Thanks. Yes, for the moment my library targets x86 only. In a later iteration I will make it architecture independent. Below System.Rtti.TRttiMethod.Invoke there is System.Rtti.TValue.Cast which seems unable to handle the untyped pointer to the Record. Exception EInvalidCast is thrown. Is the Rtti Invoke method capable to invoke the method with an untyped pointer or have I somehow convert the untyped pointer to a typed pointer by making use of some other Rtti functions? Kind regards André Share this post Link to post
Leif Uneus 43 Posted June 24, 2022 const records with size less or equal to a pointer is passed by value. But there is an exception since Delphi Rio. A record larger than 4 bytes is passed by reference on all platforms. See https://stackoverflow.com/a/53697759/576719 Share this post Link to post
Andre1 0 Posted June 24, 2022 By adding 2 more integer fields to record: TDateRec = record Year: Integer; Month: Integer; Day: Integer; end; the error messages changes to "var- and out arguments must exactly match". Is my understanding correct, that rtti does not support calling the method and passing the record reference as untyped pointer? Kind regards André Share this post Link to post
Fr0sT.Brutal 900 Posted June 27, 2022 On 6/23/2022 at 10:37 AM, Fr0sT.Brutal said: When record argument is declared as const, Delphi always passes it by reference (pointer). Hmm, seems I remembered wrong. No declaration => value always Var/out => reference always Const => value if size <= pointer size (4 bytes on all platforms since Rio), reference if larger Share this post Link to post
Fr0sT.Brutal 900 Posted June 27, 2022 Okay I dove a little bit deeper (never touched RTTI before). It seems strictly tied to types so free pointer casting won't help. However I likely managed to get correct results program foo; {$APPTYPE CONSOLE} uses System.SysUtils, System.Classes, System.rtti, System.TypInfo; type // enlarge record to be always passed by ref TDateRec = record Year: Integer; Dummy: Double; end; TExample = class public function Calc(const dateRec: TDateRec): Integer; end; function TExample.Calc(const dateRec: TDateRec): Integer; begin result := dateRec.Year + round(daterec.Dummy); // use 2nd field end; var ex: TExample; dateRec: TDateRec; intResult: Integer; function executeInstanceMethod(Reference: NativeUInt; const AName: string; const Args: array of TValue): TValue; var context: TRttiContext; instType: TRttiInstanceType; params: TArray<TRttiParameter>; obj: TObject; Arg: TValue; p: Pointer; begin context := TRttiContext.Create; try try obj := TObject(Reference); instType := (context.GetType(obj.ClassType) as TRttiInstanceType); // get the actual pointer - didn't find another way to TValue.GetAsPointer Args[0].ExtractRawData(@p); // learn method's parameters params := instType.GetMethod(AName).GetParameters; // make new TValue with proper type TValue.Make(p, params[0].ParamType.Handle, Arg); result := instType.GetMethod(AName).Invoke(obj, [Arg]); except on E: Exception do writeln(E.ClassName + ' error raised, with message : ' + E.Message); end; finally context.Free; end; end; function executeInstanceMethodReturnIntArgsStruct(Reference: NativeUInt; const AName: PAnsiChar; Reference2: Pointer): Integer; begin result := executeInstanceMethod(Reference, string(AName), [Reference2]) .AsInteger; end; begin try ex := TExample.Create; dateRec.Year := 2022; dateRec.Dummy := 1; writeln( executeInstanceMethodReturnIntArgsStruct(NativeUInt(ex), 'Calc', @dateRec) ); except on E: Exception do writeln(E.ClassName, ': ', E.Message); end; Readln; end. Works on my XE2, prints "2023" 1 Share this post Link to post
Andre1 0 Posted June 27, 2022 @Fr0sT.Brutal Thank you so much Kind regards André Share this post Link to post
Fr0sT.Brutal 900 Posted June 28, 2022 You can also press "Like" on the post to increase my rep 🙂 Addition: as you develop generic wrapper, adding record's size check would save you from mysterious bugs in the future. 1 Share this post Link to post