The ActiveX Shell technologyeasy way to enable ActiveX scripting on Delphi and C++ Builder application |
This is technology for Delphi and C++ Builder.IntroductionIt allows to create ActiveX object from any Delphi object at design time and at RUN-TIME by one function call. That allows OLE automation to access properties and methods of Delphi object.
There is nothing to coding with this technology. Just call one function and use ALL your objects as true ActiveX object now.
The technology can be used, for example, to embed scripting language such as VBScript, JScript into the application. With scripting based on MS ActiveX Scripting or VBA.
In this document you can find all what you need for that.
It is known that Delphi and C++ Builder have own object model which is distinct from object model of MS Windows named COM / DCOM / ActiveX. Therefore to apply Delphi objects as ActiveX objects it is necessary each time to create ActiveX Library and separated Automation Object object as wrapper. But will be very problematic to make it for all classes used by application. Moreover that will not be Delphi application.PossibilitiesThe ActiveX Shell technology overcomes this misunderstanding and makes properties and methods of Delphi objects accessible for OLE automation that has a huge range of use.
The technology allows to create special Active Shell objects. ActiveX Shell object is true ActiveX object. It is wrapper around Delphi object.
Delphi object + ActiveX Shell = ActiveX object Typical example of use is an embedding a scripting language into the application by using Microsoft ActiveX Scripting or VBA. Where in role of the scripting language can be used VBScript, JScript, ActivePerl or any other maintained MS ActiveX Scripting.
1.Windows applications supporting OLE
2.Windows Scripting Host
3.Office VB
4.Internet Explorer
5.ASP
...| OLE automation <- MS ActiveX Scripting <- JScript, VBScript, Perl etc | | | | ActiveX Shell objects
[ threadsafe ]Delphi application's
internal scripts| Delphi objects But MS ActiveX scripting is only one of ways to use application's object hierarchy created with the ActiveX Shell technology. Now you can use anyone supporting AciveX model.
For example, Windows Scripting Host is new MS technology that allows to use JScript and VBScript programs as Windows batch file. It uses an ActiveX object model too. Other examples - IIS ASP, Office VB code, VBA.
From side of OLE automation it is enough to create one ActiveX Shell object to receive access to all other linked Delphi objects in application which is accessible from first as properties, child-components, methods results. That means we receive a not only one ActiveX object but access to all objects in the hierarchy - an OBJECT MODEL OF APPLICATION.
What does it mean ?! For example, if script engine has a reference to Form1 created with ActiveX Shell then all other components on the form accessible in script as properties, child-components or as methods results like this:
Form1.Panel1.Caption
Form1.Panel1.Font.Name
Form1.DBGrid1.Columns.Item(1).Title
Form1.Query1.SQL.Text
Form1.WindowState
...This is same object hierarchy as you use in Delphi code.
Form1 |____ Panel1 | |-- Caption | |-- Font | |-- Size | |-- Color | |-- Name |____ Panel2 | |-- ... | |____ Button1 | |-- ... | |____ DBGrid1 | |-- Visible | |-- Columns | |-- Item(i) | |-- FieldName | |-- Title | |-- Font | |- ... | |____ Query1 | |-- SQL | | |- Text | | | |-- Active | |-- ...How to obtain a first reference to ActiveX Shell object in your script ?! There are some ways.
For internal scripts you have just to add name first name to the script engine's name space.
As you know, if application is designed as OCX library's object then it is possible to execute your application by calling CreateOLEObject (CreateObject in VB) and to use its objects in Visual Basic program, VBScript, JScript, VBA code of any Windows applications supporting OLE such as Word, Excel, Outlook, Internet Explorer, IIS ASP etc. This is second way to obtain a reference to first ActiveX Shell object.
Technology allows to create ActiveX object from any Delphi object at design time and at RUN-TIME by one function call. That allows OLE automation to access properties and methods of corresponding Delphi object. See below how to create ActiveX Shell object for your Delphi object.ActiveX Shell object is TRUE ActiveX object. From the point of view of OLE a ActiveX Shell object have same properties and methods as defined in corresponding native Delphi object. OLE works with native Delphi object by its ActiveX Shell object.
Now it is possible through this mechanism:
For object received as result of first two listed actions a new ActiveX Shell object will created automatically. To return an object as result of ActiveX Shell methods you have to create new ActiveX Shell object manually by calling obj_to_variant or obj_to_activex function in the method implementation.
- to get and set values of published properties
- for classes inherited from TComponent to receive reference to child-components by name
- for classes implementing Iactivex_shell_executable to call methods with variable number of arguments
- raising an exception in those methods as in normal Delphi methods
Now it is possible too:
ActiveX Shell object is true ActiveX object. Therefore to reference such object in Delphi is used Variant variable. Function var_is_object described below is used to check that Variant contains ActiveX Shell object.
- to store and return Delphi objects in Variant variable
- to receive object with reference counting and autodestruction features, see below
When having ActiveX Shell object it is possible anytime to access corresponding Delphi object by calling function var_to_object described below.
When you are using obj_to_variant and obj_to_activex it creates automatically for some Delphi classes extended ActiveX Shell object for receiving access to basic features of those Delphi objects via OLE automation. It is if the class is frequently used in Delphi but have not sufficient published properties to manipulate. Technology allows you to define such extended ActiveX Shell objects for any Delphi class. See below in detail.
Multitread support
As you know, Delphi (VCL) object has not multithread support and usually you have to use Synchronize or other methods to synchronize access to the object.
All request to Delphi object via ActiveX Shell objects is always automatic synchronized. That allow to create multithread application without care about threads synchronization. The technology garantees that only one thread in multithread framework can access some Delphi object through OLE at one time even if you create more then one ActiveX Shell object for one Delphi object. But, of course, any two different Delphi objects work independently.
Thread 1 (main)
ActiveX Shell for same Object1 Thread 2
ActiveX Shell for same Object1 Thread 3
ActiveX Shell for same Object1 \
waiting\|
waiting
| /working
/
Delphi Object1
Limitations of the technology are based on the Run Time Type Information's features.Available versions, downloadingThe request on TPersistent is explained by availability the Run Time Type Information (RTTI) for the object. Though classes inherited from TPersistent is the majority of Delphi classes. And practically absolutely all published properties of Delphi objects are simple data types or reference to classes inherited from TPersistent. For example, TComponent, TPicture, TGraphic, TFont, TStrings, TCollection, TDataSet are inherited from TPersistent.
- The technology works only for classes inherited from TPersistent.
- It is necessary to have one version of Delphi or C++ Builder (it means RTTI) for all separately compiled modules (.exe, .dll, .ocx etc) in application if they use the given technology together.
- ActiveX Shell object can't know when corresponding Delphi object destroy itself or by other objects.
Available compiled versions for:How to install
- C++ Builder 3
- C++ Builder 4
- C++ Builder 5
- Delphi 3
- Delphi 4
- Delphi 5
Source code is compatible with Delphi 3 or above, C++ Builder 3 or above.Latest version of the component is always available from the components download page.
Usage
1. Unzip archive axshell.zip with subdirectories. 2. Directory CB3\ is for CBuilder 3 users. File activex_shell.obj.
Directory CB4\ is for CBuilder 4 users. File activex_shell.obj.
Directory CB5\ is for CBuilder 5 users. File activex_shell.obj.
Directory D3\ is for Delphi 3 users. File activex_shell.dcu.
Directory D4\ is for Delphi 4 users. File activex_shell.dcu.
Directory D5\ is for Delphi 5 users. File activex_shell.dcu.Copy file activex_shell.dcu (.obj) from one of this to any Delphi library path or to your project path.
Or install activex_shell.dcu (.obj) from Menu > Component > Install Component.
3. Just add activex_shell to uses statement when need it. Examples are located in the corresponding directories too. Also see examples below.
It is easy to use. Just call one function to create ActiveX Shell object for your Delphi objectfunction obj_to_variant(obj : TObject) : Variant;
Returned Variant datatype will be varDispatch because it is ActiveX object.
For example,
unit Unit1; interface
uses
activeX_shell;type
TForm1 = class (TForm)
Button1: TButton;
procedure Button1Click (Sender: TObject);
private
end;implementation
{$R *.DFM}
procedure TForm1. Button1Click (Sender: TObject);
var v: Variant;
begin// creating ActiveX Shell object for this form
v := obj_to_variant(Self);// using published properties
v.Caption := 'New caption';
v.Font.Size := 22;// using child-components by name -
// Button1 is not a published property
v.Button1.Caption := 'After click caption';// using an object type properties
v.ActiveControl := v.Button1;// String representation is used to read and write enumeration datatype
v.WindowState := 'wsMinimized';
ShowMessage(v.WindowState);v.WindowState := 'wsNormal';
ShowMessage(v.WindowState);end;
end.
That is all you need for using published properties and for accessing child-components by name.
Note that we are using a Variant reference in this example to show that we work with the ActiveX objects. Variant in Delphi is used for ActiveX objects. All this code can be executed by script engine by VBA, JScript, VBScript, ActivePerl, etc. See below how to embed scripts engine the application.
Important declarations in unit activex_shell
Useful functions declared in unit activex_shell:
// checking that Variant is ActiveX Shell object
function var_is_object(value: Variant): Boolean;// getting back Delphi object from Variant containing ActiveX Shell object
function var_to_object(value: Variant) : TObject;// Getting textual representation of Variant or zero length string
function var_to_string(value: Variant): AnsiString;Example: Receiving back Delphi object from Variant containing ActiveX Shell object. Let variable param is VarArray.
var obj : TObject;
beginif var_is_object(param[i]) then obj := var_to_object(param[i]);
end;
Simple way to accesss methods without arguments is to use published property reader.
TMyComponent = class(TComponent)
private
function runMethod1 : String;
published
property Method1 : String read runMethod1;
end;As you see, runMethod1 is called every time when accessing published property Method1.
More flexible way to access the methods is to use special interface declared in unit activex_shell as:
Iactivex_shell_executable = interface(IUnknown)
['{B0BA1D30-6F55-11D3-B11C-F9DBB0614516}']// to return a comma delimited list of methods supported by your object
function activex_shell_methods : AnsiString;// to execute objects methods with variable number of arguments
function activex_shell_exec(method_name: AnsiString; var param: Variant): Variant;
end;When implementing Iactivex_shell_executable interface it is possible to use methods with variable number of arguments. The interface allows to define what methods of your class will accesible from point of view of OLE automation. Function named activex_shell_methods returns a comma separated list of methods which you want to publish. Function activex_shell_exec is a implementation of those methods.
Methods arguments:
Use standard Delphi functions VarIsArray and VarArrayHighBound to check that Variant is an array and for determining size of the array:
- method_name is a name of a called method
- method_name is always on lower case
- param value is Unassigned or VarArray - array [0.. n] of Variant
Example of work with methods via Iactivex_shell_executable
As you know, in Delphi to check that the object implements some interface is used IUnknown interface (see Delphi manual). Therefore two interfaces are requared: Iactivex_shell_executable and IUnknown.
Some Delphi classes are already implements IUnknown. For example, TComponent class and all its descendants in Delphi 4 and later, C++ Builder 3 and later. In other cases you have to implement IUnknown manually in each class that uses Iactivex_shell_executable interface.
The other way to provide IUnknown functionality to your class is to use TAXObject instead of TObject and TAXComponent instead of TComponent as base classes in your code. Those classes contain already IUnknown implementation. Defined in unit activex_shell as:
// Useful as base class instead of TObject
TAXObject = class(TPersistent, IUnknown)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;// Useful as base class instead of TComponent
TAXComponent = class(TComponent, IUnknown)
protected
{$ifdef VER100} // only for Delphi 3
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{$endif}
end;Examples, how to provide IUnknown in your class:
Examples Description // Declaration you have TMyObject = class
//...
end;// To provide IUnknown write
TMyObject = class(TAXObject)
// ...
end;
TAXObject is used. Now real base class now is not TObject but TPersistent. That means Delphi now will create RTTI for your object. // Declaration you have TMyComponent = class(TComponent)
//...
end;// To provide IUnknown write
TMyComponent = class(TAXComponent)
//...
end;TAXComponent is used. It is not needed in Delphi 4 and above, C++ Builder 3 and above. Do it just for compatibility with Delphi 3. // Declaration you have TMyClass = class(TMyBaseClass)
// ...
end;// To provide IUnknown write
TMyClass = class(TMyBaseClass, IUnknown)
protected
function QueryInterface(const IID: TGUID;
out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
implmentation
uses
Windows;function TMyClass.QueryInterface(const IID: TGUID;
out Obj): Integer; stdcall;
begin
if GetInterface(IID, Obj)
then Result := 0
else Result := E_NOINTERFACE;
end;function TMyClass._AddRef: Integer; stdcall;
begin
Result := 0;
end;function TMyClass._Release: Integer; stdcall;
begin
Result := 0;
end;
if TMyBaseCalss does not implement IUnknown then do it manually in TMyClass. Example implementation is here too.
This skeleton of a component which publishs three its methods as ActiveX methods by using Iactivex_shell_executable interface:
TMyComponent = class(TAXComponent,Iactivex_shell_executable)
protected
function activex_shell_methods : AnsiString;
function activex_shell_exec(method_name : AnsiString; var param: Variant): Variant;
public
procedure Method1();
function Method2(obj : TObject) : String;
function Method3(text : String) : Object;
end;implementation
// declaration of methods
function TMyComponent.activex_shell_methods : AnsiString;
begin
result := 'method1,method2,method3';
end;// implementation of methods
function TMyComponent.activex_shell_exec(method_name : AnsiString;
var param: Variant): Variant;
begin
result := Unassigned;if method_name = 'method1' then begin
Method1();
end
else if method_name = 'method2' then begin
if VarIsArray(param)
then result := Method2(var_to_object(param[0]));
end
else if method_name = 'method3' then begin
if VarIsArray(param)
then result := obj_to_variant(Method3(var_to_string(param[0])));
end;end;
Let now MyObj is a reference to an instance of TMyComponent in script engine.
Now you can write VBScript code like this:
rem Receiving a published property value
res = MyObj.Namerem Assigning a published property value
MyObj.Name = "new_name"rem Calling a procedure
MyObj.Method1()rem Receiving a string result
res = MyObj.Method2( MyObj )rem Receiving a object result
Set obj_res = MyObj.Method3( "test" )
This is a working example of use Iactivex_shell_executable interface for TForm. Additional IUnknown interface implementation here needs only for Delphi 3. You can found one in example application.
Using TStrings, TCollection, TDataSetUsage from C++ BuilderWhen you are using obj_to_variant and obj_to_activex it creates automatically for some Delphi classes extended ActiveX Shell object for receiving access to basic features of those Delphi objects via OLE automation. It is if the class is frequently used in Delphi but have not sufficient published properties to manipulate. Technology allows you to define such extended ActiveX Shell objects for any Delphi class. Note that all published properties are always accessible too.
For TStrings class and all inherited classes it is possible to use in appropriate extended "Active X Shell" object already defined properties and methods:Standard TStrings properties, see Delphi documentation
Standard TStrings methods, see Delphi documentation
- Count : Integer;
- Text : String;
Special methods for getting value of element
- function Add(value: String) : Integer;
- procedure Append(value: String) : Integer;
- procedure Clear();
- procedure Exchange(index1, index2: Integer);
- function IndexOf(value: String) : Integer;
- procedure Insert(index: Integer; value: String);
- procedure Move(CurIndex, NewIndex: Integer);
- procedure LoadFromFile(FileName: String);
- procedure SaveToFile(FileName: String);
Special method for setting value of element
- function Strings(index: Integer) : String;
- function Item(index: Integer) : String;
- procedure SetItem(index: Integer; value: String);
For TCollection class and all inherited classes it is possible to use in appropriate extended "Active X Shell" object already defined properties and methods:Standard TCollection properties, see Delphi documentation
Standard TCollection methods, see Delphi documentation
- Count : Integer;
- ItemClass : String;
Special methods for getting value of element
- function Add : TObject;
- procedure Clear();
- function FindItemID(id : Integer) : TObject;
- function Item(index: Integer) : TObject;
For TDataSet class and all inherited classes it is possible to use in appropriate extended "Active X Shell" object already defined properties and methods:Standard TDataSet properties, see Delphi documentation
Standard TDataSet methods, see Delphi documentation
- FieldCount : Integer;
- BOF : Boolean;
- EOF : Boolean;
Special methods for getting field and value of field, it correspond to properties
- procedure First;
- procedure Last;
- procedure Next;
- procedure Prior;
- function IsEmpty : Boolean;
- function FieldByName(FieldName: String) : TField;
- function FindField(FieldName: String) : TField;
- function Fields(index: Integer) : TField;
- function FieldValues(FieldName: String) : Variant;
Of course those properties and methods always return values as Variant. If result type is TObject it means Variant contains ActiveX Shell object for that TObject.
Usage from C++ Builder is same as from Delphi. With a small difference. In C++ Builder Variant is class, we have to use the class syntax. For example, in Delphi we have following code:
uses
activex_shell;....
var myRef : Variant;
begin
// creates OLE object from the form given as self
myRef := obj_to_variant( Self );// sets new caption to the form
myRef.Caption := 'Welcome !';
end;Translated to C++ Builder is looking like this:
// C++ include directives
#include "activex_shell.hpp"
#pragma link "activex_shell"...
{
// creates OLE object from the form given as this
// class Variant constructor is used
Variant myRef( obj_to_variant(this) );// sets new caption to the form
// class Variant method is used
myRef.OlePropertySet("Caption", "Welcome !");
}
Using
with MS ActiveX Scripting, embedding scripts into the application
MS ActiveX Scripting is just one of ways for using application's ActiveX object model created with ActiveX Shell technology. You can use with your application any other scripting engine that uses AciveX objects. See above about it.Creating OCX libraryFor embedding scripts on VBScript, JScript into an application are required:
MS Script Engine - already is on your computer if MSIE 4.0 or above is installed. It is possible to install separately with the distribution kit loaded from Microsoft site
- MS Script Engine
- MS Script Control
http://www.microsoft.com/msdownload/vbscript/scripting.asp
Size: ~ 650 Kb
License: Freeware
GUID: {EE09B103-97E0-11CF-978F-00A02463E06F}
MS Script Control - is necessary for use the MS Script Engine by your application. It is possible to install with the distribution kit loaded from Microsoft site
http://msdn.microsoft.com/scripting/scriptcontrol/default.htm
Size: ~ 250 Kb
License: Freeware, see site
GUID: {0E59F1D5-1FBE-11D0-8FF2-00A0D10038BC}
Hint: This function allows to do dinamic check by GUID that the necessary OLE components are installed.
function check_by_guid (component_guid: String): Boolean;
var v : Variant;
begin
try
v := CreateCOMObject( StringToGUID (component_guid) );
result := True;
except
result := False;
ShowMessage('Component is not installed: ' + component_guid);
end;
end;
This is a example of use MS Script Control in Run Time where two VBScript commands are interpreted:
In detail about using MS Script Control, its properties, methods and objects you can read in msscript.hlp from the MS ScriptControl distribution kit.
This is only if you need to execute your application as OLE object.Professional version's advantagesFor creating an OCX library with "YourAppName.Application" OLE object is used Delphi ActiveX framework:
Now you have OCX library with "YourAppName.Application" object inside.
- use Delphi menu > New then choose ActiveX > ActiveX Library
- save project as "YourAppName"
- use Delphi menu > New then choose ActiveX > Automation object
- write "Application" as Class Name
- add properties and methods in the Type Library editor and register library
Now it is possible to execute project and use its objects by call
my_object := CreateOLEObject('YourAppName.Application');in Delphi or similar function from VBA,VBScript, JScript and all other languages supporting OLE.In detail see Delphi documentation.
The "YourAppName.Application" object can have properties and methods of type OleVariant that are references to objects. They can be implemented easy by using the ActiveX Shell technology.For example, if the Automation object you created has property DataSet of type OleVariant which is added in Delphi Type library editor.
An implementation unit will contain empty method which gives the property value. You can use ActiveX Shell technology in it:
function TYourAutomationObject.Get_DataSet : OleVariant;
begin// simply return an object
result := obj_to_variant(FDataSet);end;
Now it is possible to access DataSet property and all other objects hierarchically. VBScript example:
my_object = CreateObject('YourAppName.YourAutomationObject') my_object.DataSet.First()
AmountPaid_sum = 0
do while not my_object.DataSet.EOF
AmountPaid_sum = AmountPaid_sum + my_object.dataset.FieldValues("AmountPaid")
my_object.dataset.Next()
loopMsgBox(AmountPaid_sum)
It is a beautiful decision for one more problem.
It is known that only descendants of TWinControl can be automatically transformed by Delphi to ActiveX. What to do when need to provide functionality of other classes that are not controls ?
Simply create one Automation object with property that returns an object of Delphi class you need. That allows to receive functionality of any Delphi class in scripts. It was described above for TDataSet.
I. Multithread support is in professional version only.Using with HTML Template component to create HTML, XML, SGML reports
II. In professional version it is possible to create ActiveX Shell object with reference counting and without caring about clearing of corresponding Delphi object. The Delphi object will be auto destroyed when reference count is 0.This feature is very powerful, for example, when returning value from function:
function some_function ... : Variant;
var strings : TStrings;
begin// creating some object
strings := TStringList.Create();strings.Add('line 1');
strings.Add('line 2');
strings.Add('line 3');// creating and returning as result an ActiveX Shell object
// with feature of reference counting
result := obj_to_activex( strings );// You have not to care about destruction of TStringList object.
// It will be auto destroyed when will be not needed.end;
Note that
function obj_to_activex( obj : TObject ) : Variant;
is same as considered above obj_to_variant .
But function obj_to_activex create ActiveX Shell object with feature of reference counting and autodestruction.
This feature is very flexible not only in ActiveX Shell methods implementation, it can be used freely in application to return or store any objects without caring about clearing it. The object exists while exist one or more Variant reference to it.
Corresponding Delphi object can be received back from the Variant reference by using var_to_object function.
If you need to create in application HTML, XML, SGML or text reports then use HTML Template component. It is available for downloading. In detail see its manual.It works like well known ASP, JSP, PHP technologies and allows to produce HTML (XML, SGML, text) page from page template stored in file, database etc. However it works at application side without Web server engine and can access application's objects and data.
To design report view it is possible to use your faivorite editor. For example, Netscape Composer, MS FrontPage, MS Word, Notepad and so on.
Template page is formatted page with embedded script. The ActiveX Shell technology can be used as engine for the scripts execution.
How to buy
and download the source code
For commercial purposes and if you are interested in sources you have to buy the professional version.Contact information
Software will available at once after the registration from our secure web site.
The ActiveX Shell technology
(professional version, source code, compatible with D3 and above, CB3 and above)Personal license,
single user or one computer40 $ Buy it Company license,
any number of users and computers within one company90 $ Buy it
Contact person: Karim YusupovUseful Internet linksSend your questions and comments to support@apelseen.com
Apelseen software website is http://www.apelseen.com
Version 2.1
Copyright (c) 1999, Apelseen software. All Rights Reserved