Importing Delphi Units

Top  Previous  Next

To use Delphi language constructs, such as classes, objects, global variables and procedures, in a script you have to import its enclosing unit, and then register this unit in the script execution environment using TLMDScriptControl.AddUnit method. LMD-ScriptPack provides the Importing Wizard that can be accessed from Delphi Tools menu.

 

For every imported Delphi unit the Importing Wizard creates a corresponding wrappers unit. For example, for MyUnit.pas the importer will create MyUnit_LMDSW.pas wrapper unit. The postfix is an abbreviation of "LMD Script Wrappers".

Using Importing Wizard

 

Let's start with the example of importing a Delphi unit to make its contents available to scripts.

 

Launch the Delphi IDE, and create new Application using File|New|VCL Forms Application menu item. Save the application somewhere. The newly created application will contain a unit Unit1.pas that we will import. First have a look at the unit itself, it contains following declarations:

-A uses clause
-TForm1 form class declaration
-Form1 global constant

 

Now switch to the importer expert view, click on "LMD-Script Import" lower tab in Delphi's editor window:

 

 

You will see LMD-ScriptPack Importer Wizard:

 

 

At the left you will see the tree of unit declarations, at the right – a short description of currently selected item. The Item errors list-box contains errors and warnings that correspond to currently selected item. The whole list of errors and warnings will appear in standard Delphi's message view. You can click the error message to select corresponding item.

 

Now, the importer does not show any errors, so you can click Generate button to generate the wrappers unit. The importer will generate Unit1_LMDSW.pas unit in the same directory as original Unit1.pas unit.

 

Before discovering wrappers unit, let's add something to our form. Place the edit (Edit1) control on the form. Then, add two public methods to the form class declaration:

 

type  

  TForm1 = class(TForm)

    Edit1: TEdit;

  private

    { Private declarations }

  public

    { Public declarations }

    procedure MyProc;

    procedure MyProc2(const X);

  end;

 

Then, press Shift+Ctrl+C to generate method bodies and add some implementation:

 

procedure TForm1.MyProc;

begin

  ShowMessage('MyProc method called. Edit1 text is: ' + Edit1.Text);

end;

 

procedure TForm.MyProc2(const X);

begin

  ShowMessage('MyProc2 method called');

end;

 

Save the changes, and open importing expert again. Press Generate button to refresh expert content. You will see three new nodes in the unit structure tree: Edit1, MyProc and MyProc2:

 

 

Now you will see an error message. Through LMD-ScriptPack wrappers supports most of the Delphi language constructs, there are some constructs that are not supported. In our example this is 'X' un-typed parameter. The importer expert shows items that contains errors with a grayed check-box.

You should uncheck such items or somehow fix the problem. In our example we just uncheck the MyProc2 item:

 

 

Remember to press Generate button again. And now Unit1_LMDSW.pas wrappers unit is regenerated.

Note, that checked items will be accessible from the script, but unchecked – are not. So, eventually you might uncheck items not only when they contain errors, but when you do not want to provide access to them from the script.

 

While saving an original unit (Unit1.pas) importing expert saves another one file: Unit1.uim (Unit Importing). This file stores additional data about importing unit, for example, what items was unchecked and what – are not. This file is always saved when you save original unit in Delphi IDE. If you open importing expert next time, you will see that previously unchecked items remains unchecked.

 

Another one file is Unit1.usu (Used Unit). This file is saved each time wrappers unit successively generated. This file contains additional information of the generated wrappers unit and it is used by importer expert when importing other units that uses the original one (in 'uses' clause). This file should be treated as intermediate output similar to Delphi *.dcu files.

Discovering Wrappers Unit

 

Let's now discover generated wrappers unit. You can see following declarations:

-TForm1_sw
-Unit1_sw

 

There are wrappers for TForm1 type and for the Unit1 itself. Postfix '_sw' was choused to be short. It used consistently for all wrapper-types and means Script Wrapper.

 

The main role of wrapper-types is to allow conversions from/to OleVariant type that is used by TLMDScriptControl.

There are two very simple functions contained in every wrapper-type: ToVar and FromVar:

 

var

  F: TForm1;

begin

  LMDScriptControl1.AddObject('MyForm', TForm1_sw.ToVar(Self));

  LMDScriptControl1.Active := True;

  

  F := TForm1_sw.FromVar(LMDScriptControl1.Eval('MyForm'));

end;

 

So, you typically use ToVar when:

-Adding named Delphi object to the script.
-Calling script function with parameters, in argument array.

 

You typically use FromVar when:

-Getting result of evaluated expression.
-Getting result of called function.

 

Wrapper-types generated for all declared in original unit types. This means that you can work not only with Delphi objects, but with records, sets, enumerations, ect. Simple Delphi types, like Integer or Double can be converted to/from OleVariant without using ToVar/FromVar wrapper-type methods, but you always should use there methods for other types.

 

Wrapper-type is also generated for unit itself. Use Unit1_sw.ToVar method to create a unit IDispatch pseudo-object. All global variables and functions will be visible as properties and methods of this object in the script. All declared in unit types are also become visible from the script as properties of this pseudo-object. So, for example, you can write following script code:

 

VBScript source:

 

Set FormClass = Unit1.TForm1 'declared Type

Set Form      = Unit1.Form1  'Global variable

 

Calling Delphi code:

 

LMDScriptControl1.AddUnit(Unit1_sw, True);

LMDScriptControl1.Active := True;

 

You can actually omit 'Unit1.' prefixes, because APublishMembers parameter of AddUnit function was set to True:

 

Set FormClass = TForm1

Set Form      = Form1

 

Note that AddUnit function used in previous example is equivalent to:

 

LMDScriptControl1.AddObject(Unit_sw.GetUnitName, 

  Unit1_sw.ToVar(LMDScriptControl1), True);

 

You can also use AddUnits function to add several units at once, or use AddAllUnits function to add all used in project wrapper units to the script.

 

Notes about class-type wrappers

 

Some special notes must be said about wrapper-type generated for Delphi classes. For every class, in addition to wrapper-type importing expert generates 'class of' type declaration:

 

TEdit_sc = class of TEdit;

 

And wrapper-type contains additional two functions: ClassToVar and ClassFromVar. There functions allow working with so-called Delphi meta-classes.

 

Notes about inheritance

 

The wrapper-types for classes fully support inheritance. So, you can pass as a parameter to ToVar function not only an instance of the corresponding class, but an instance of any its descendant. Appropriate IDispatch wrapper will be created automatically. For example, if you add memo to the script using following code:

 

LMDScriptControl1.AddObject('MyMemo', TObject_sw.ToVar(Memo1));

 

All imported TMemo methods and properties, like Lines property, will be accessible to the script anyway. But, for the TMemo method to be accessible StdCtrls_LMDSW.pas unit still must be included in 'uses' clause of some project unit.

If the wrapper-type, corresponding to the class of passed instance is not registered, Script Pack tries to search for a wrapper-type for instance class ancestor. And so on, recursively. If more specific type-wrapper can't be found, the wrapper-type of TObject class will be selected.

 

The FromVar function also supports inheritance and, actually, can return an instance of original class descendant.

 

Inheritance is also supported in ClassToVar/ClassFromVar meta-class functions.

 

Notes about procedural-type (event) wrappers

 

Procedural-types in Delphi are used mainly for declaring event types. The example of procedural type is TNotifyEvent type.

 

For every procedural type, in addition to wrapper-type importing expert generates 'script event handler' class:

 

TNotifyEvent_sh = class(TLMDScriptEventHandler)

  …

end;

 

This class is used internally, and not indented for user.

 

Like other wrappers procedural-type wrappers contain FromVar and ToVar functions for converting wrote in Delphi event handlers from/to OleVariant. Note that this is about event handlers, wrote in Delphi.
 

However, the power of LMD-ScriptPack is that it allows you to write event handler in script code. To achieve this you should use additional procedural-type wrapper method - GetHandler. For example, we can write OnClick button event handler in script code:

 

VBScript source:

 

Sub Button1Click(Sender)

  MsgBox “Hellow World!”

End Sub

 

Calling Delphi code:

 

Button1.OnClick := TNotifyEvent_sw.GetHandler(

  LMDScriptControl1, 'Button1Click');

LMDScriptControl1.Active := True;

 

If you use LMD-ScriptPack together with LMD-DesignPack module, you can use TLMDScriptControl.HookEvent method in TLMDModule.OnHookEvent event handler.

HookEvent method creates a script event handler, automatically choosing procedural-type wrapper class based on passed through APropInfo parameter event property RTTI. Again, as with inheritance, corresponding *_LMDSW.pas unit must be included in 'uses' clause of some project unit. If the corresponding procedural-type wrapper can't be found, LMD-ScriptPack will raise an 'Event type is not registered' exception.