• No results found

If truth be told, exposing an existing function using TBindingMethodsFactory is a little tedious. Thankfully, alongside it

stands the WrapObject function of System.Bindings.ObjEval: pass this routine an object of your choice, and an IScope

implementation is returned that exposes almost everything that can be retrieved from the object using RTTI. Assuming the default RTTI settings are in operation, that means practically every public instance method and property, together with every field (class methods are not exposed however). Moreover, in the case of an object with child objects, those are automatically exposed too.

To see this in effect, create either a new VCL or FireMonkey HD application, then place a TLabel, TEdit and TButton on the

Next, handle the button’s OnClick event like this:

uses

System.Bindings.EvalProtocol, System.Bindings.Evaluator, System.Bindings.EvalSys, System.Bindings.ObjEval,

System.Bindings.Methods;

procedure TForm.Button1Click(Sender: TObject); var Objects: TDictionaryScope; Scope: IScope; Output: IValue; begin Objects := TDictionaryScope.Create; Objects.Map.Add('Application', WrapObject(Application)); Objects.Map.Add('Screen', WrapObject(Screen)); Scope := TNestedScope.Create(BasicOperators, TNestedScope.Create(BasicConstants, TNestedScope.Create( TBindingMethodsFactory.GetMethodScope, TNestedScope.Create(Objects, WrapObject(Self))))); Output := Compile(Edit1.Text, Scope).Evaluate(Scope, nil, nil); ShowMessage(Output.GetValue.ToString);

end.

Here, we expose the basic operators and constants provided by System.Bindings.EvalSys as the outer-est scope and

second most outer scope respectively, followed by the standard functions provided by TBindingMethodsFactory, the Application and Screen objects defined by the relevant Forms unit (either FMX.Forms or VCL.Forms), and finally, the members

of the form itself, which will be callable by an expression without fully qualifying with the form’s name and a full stop. On running the application, you should be able to enter any of the following expressions into the edit box, and it work as expected:

"The main form's caption is " + Application.MainForm.Caption "You've just clicked a button labelled '" + Button1.Text + "'"

"The same button is called '" + Application.MainForm.Button1.Name + "'"

'The first (and only?) form of the application has ' + ToStr(Screen.Forms[0].ComponentCount) + ' controls'

"The name of this form's first control is '" + Components[0].Name + "', and its text is '" + Components[0].Text + "'"

Notice how child objects are exposed in a ‘dynamic’, script-like fashion — the third example here wouldn’t be legal in compiled code, given Button1 is an object in your own form, not the base class that Application.MainForm is typed to.

Expression engine quirks: brackets vs. no brackets

Consider the following code, which will raise an EEvaluatorError exception:

uses System.SysUtils, System.Bindings.EvalProtocol, System.Bindings.EvalSys, System.Bindings.Methods, System.Bindings.Evaluator, System.Bindings.ObjEval; const Expr = 'DateTimeToStr(Now)'; var Scope: IScope; Output: IValue; begin Scope := TBindingMethodsFactory.GetMethodScope;

Output := Compile(Expr, Scope).Evaluate(Scope, nil, nil); WriteLn(Output.GetValue.ToString);

end.

functions implemented by TBindingMethodsFactory.

So, let’s implement them ourselves with a helper class and WrapObject:

type

TDateTimeUtils = class

function DateTimeToStr(const ADateTime: TDateTime): string; function Now: TDateTime;

end;

function TDateTimeUtils.DateTimeToStr(

const ADateTime: TDateTime): string;

begin

Result := System.SysUtils.DateTimeToStr(ADateTime);

end;

function TDateTimeUtils.Now: TDateTime; begin Result := System.SysUtils.Now; end; const Expr = 'DateTimeToStr(Now)'; var ScopeSource: TDateTimeUtils; Scope: IScope; Output: IValue; begin ScopeSource := TDateTimeUtils.Create; try Scope := TNestedScope.Create( TBindingMethodsFactory.GetMethodScope, WrapObject(ScopeSource));

Output := Compile(Expr, Scope).Evaluate(Scope, nil, nil); WriteLn(Output.GetValue.ToString); finally ScopeSource.Free; end; end. Run the revised application, and it should now go through without exceptions. However, 30/12/1899 (or 12/30/1899 if you speak American) will be returned! This is caused by a combination of two things: the expression parser requires parameterless functions to be called with a pair of empty brackets, and the evaluator’s ‘lazy lookup’ of object members returns nil instead of raising an exception

when the source expression references a member that doesn’t exist.

In our case, the problem can be fixed in one of two ways: ensure the source expression uses brackets (i.e., require

'DateTimeToStr(Now())' not 'DateTimeToStr(Now)'), or make Now a property rather than a parameterless function:

type

TDateTimeUtils = class strict private

function GetNow: TDateTime; public

function DateTimeToStr(const ADateTime: TDateTime): string; property Now: TDateTime read GetNow;

end;

function TDateTimeUtils.GetNow: TDateTime; begin

Result := System.SysUtils.Now;

end;

Once made a property, brackets now cannot be used when referring to it inside an expression. While this is consistent with Delphi itself (you can’t use MyForm.Caption() to retrieve the form’s caption, for example), it has the implication of

12. Runtime type information (RTTI)

‘Runtime type information’ (RTTI) is the mechanism by which information about the types used by your program can be retrieved at runtime. It is also the system by which you can call methods and change property and field values without knowing at compile-time what methods and properties exactly you will be calling or changing, e.g. via name strings filled out at runtime. For historical reasons, Delphi has two main ‘levels’ of RTTI support: basic RTTI, which was there from the beginning, and ‘extended’ RTTI, which was introduced only more recently. ‘Basic’ RTTI covers as much as is necessary for the core VCL, which is to say, quite a bit for its time, but a pale shadow of the ‘reflection’ or ‘introspection’ capabilities of popular managed languages. ‘Extended’ RTTI substantially corrects this, though at the cost of producing executables that are comparatively ‘bulked up’ with all the extra type information. For this reason, the amount of extended RTTI produced is configurable; in the following pages, I will assume the (reasonably comprehensive) defaults.