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