The "ActiveX Shell" technology


easy way to enable ActiveX scripting on Delphi and C++ Builder application

Table of contents

What is this
This technology is for Delphi and C++ Builder.

It 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.
 

Introduction
It is known that Delphi and C++ Builder have own object model which is distinct from object model of MS Windows named COM / DCOM / OLE / ActiveX. Therefore to apply Delphi objects as ActiveX objects it is necessary each time to create ActiveX Library and separated ActiveX object as wrapper. But will be very problematic to make it for all objects used by Delphi application. Moreover that will not be Delphi application.

"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. It makes accessible all old objects of      Delphi application without additional programming.

Typical example of use is an embedding a scripting language into the application by using Microsoft ActiveX Scripting. Where in role of  the scripting language can be used VBScript, JScript 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" object
[ threadsafe ]
Delphi application's
internal scripts
|
Delphi objects

MS ActiveX scripting is only one of ways to use application's object model created with "ActiveX Shell" technology. Now you can use with your application any other scripting engine that support AciveX model.

Windows Scripting Host is new MS technology that allows to create JScript and VBScript program as Windows batch file. It can use an application's object model created by "ActiveX Shell" technology too.

If you design your application as OCX library object then is possible to execute your application and to use its objects in Visual Basic program, VBA, any Windows application supporting OLE such as Word, Excel, Outlook, Internet Explorer, IIS ASP etc.

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. That allow to create not only one ActiveX object but full 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 method results like this:

Form1.Panel1.Caption
Form1.Panel1.Font.Name
Form1.DBGrid1.Columns.Item(1).Title
Form1.Query1.SQL.Text
Form1.WindowState
...
Form1
  |____ Panel1
  |       |-- Caption
  |       |-- Font
  |            |-- Size
  |            |-- Color
  |            |-- Name
  |____ Panel2
  |       |-- ...
  |
  |____ Button1
  |       |-- ...
  |
  |____ DBGrid1
  |       |-- Visible
  |       |-- Columns
  |              |-- Item(i)
  |                    |-- FieldName
  |                    |-- Title
  |                    |-- Font
  |                         |- ...
  |
  |____ Query1
  |       |-- SQL
  |       |    |- Text
  |       |
  |       |-- Active
  |       |-- ...

 

Possibilities
It 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 the Delphi object.

See below how to create "ActiveX Shell" object for your Delphi object.

An "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.

Now it is possible too:

An "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.

When having "ActiveX Shell" object it is possible anytime to access corresponding Delphi object by calling function var_to_object described below.

For some Delphi classes it creates automatically extended "ActiveX Shell" object for receiving access to basic features of Delphi object via OLE automation. 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.


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 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

Limitations of the technology are based on the Run Time Type Information's features. The 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.
Available versions, downloading
Available compiled versions for: Compiled versions for other Delphi and C++ Builder releases is not available now.
Use the source code with those releases.

Source code is compatible with Delphi 3 or higher and C++ Builder 3 or higher.

Latest version of the component is always available from the components download page.
 

How to install
 
1. Unzip archive axshell.zip with subdirectories.
2. Directory CB3\ is for CBuilder 3 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 source code is located in the corresponding directory ( CB3\, D3\, D4\, D5\ ) too.
Also see examples below.
 

Usage
It is easy to use. Just call one function to create "ActiveX Shell" object for Delphi object

function 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.



 
 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: let variable obj is of type TObject and param is VarArray
// if var_is_object( param [i] ) then obj := var_to_object( param [i] );
 

How to use methods

For using "ActiveX Shell" methods it is necessary to implement Iactivex_shell_executable interface. It is declared in unit activex_shell. Interface declaration in unit activex_shell:
 
Iactivex_shell_executable = interface(IUnknown) 
  ['{B0BA1D30-6F55-11D3-B11C-F9DBB0614516}']

  // to return a comma delimited list of methods supported by 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

// Notes:
// 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

// Hint:
// Use standard Delphi functions to check
// that Variant is an array and for determining size of the array:
// VarIsArray( param)
// VarArrayHighBound( param, n )
 

Example of work with methods

For all classes implementing Iactivex_shell_executable interface it is possible to use methods with variable number of arguments. IUnknown interface is always required to check that some reference contains some interface pointer.

In Delphi 4 and later TComponent class and all its descendants implement already IUnknown. In other cases you can implement IUnknown manually in each class that uses Iactivex_shell_executable interface. Or you can use TAXObject insted of TObject and TAXComponent instead of TComponent as base classes in your code.

Useful classes TAXObject and TAXComponent are defined in unit activex_shell:
 
// 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} // extension 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;

 
 
 
This is example of use Iactivex_shell_executable interface in Delphi 3. Additional IUnknown interface implementation here needs only for Delphi 3. Because in Delphi 4 and later TComponent class and all its descendants as TForm implement already IUnknown.
 
unit Unit1; 

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, 
  Controls, Forms, Dialogs, StdCtrls, ExtCtrls,
  activeX_shell; 
// ^^^^^^^^^^^^^

type
  TForm1 = class (TForm, Iactivex_shell_executable)
//                        ^^^^^^^
    Button1: TButton; 
    procedure Button1Click(Sender: TObject); 
  private
  private

    // Iactivex_shell_executable
    function activex_shell_methods : AnsiString;
    function activex_shell_exec(method_name : AnsiString; var param: Variant): Variant;

    // IUnknown - it needs in Delphi 3 only
{$ifdef VER100}
    function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
{$endif}

  end

implementation

{$R *.DFM} 

procedure TForm1. Button1Click(Sender: TObject); 
  var v: Variant; 
begin

// creating "ActiveX Shell" object
  v := obj_to_variant(Self); 

// using "ActiveX Shell" methods
  v.show_arguments('only one arg');
  v.show_arguments('two', 'args');
  v.show_arguments('now', 3 ,'args');

  ShowMessage( v.object_classname( v.Button1 ) );

end;

// Iactivex_shell_executable

// definition of "ActiveX Shell" methods
function TForm1.activex_shell_methods : AnsiString;
begin
  result := 'show_arguments,object_classname';
end;

// implementation of "ActiveX Shell" methods
function TForm1.activex_shell_exec(method_name : AnsiString; 
                                   var param: Variant): Variant;
 var  i : Integer;
        temp_str : String;
begin
  result := Unassigned;

  //show_arguments
  if method_name = 'show_arguments' then begin

    if VarIsArray(param) then begin
      for i := 0 to VarArrayHighBound(param, 1) do begin
        temp_str := temp_str + 'arg' + IntToStr(i) + '=' 
                    + var_to_string( param[i] ) + #13;
      end;
    end;

    ShowMessage(temp_str);

  end

  // object_classname
  else if method_name = 'object_classname' then begin

// How to raise an exception:
    if VarIsArray( param ) and (var_to_object( param[0] ) = nil) then begin
      raise Exception.Create('This is nil !');
    end;

    if VarIsArray( param ) and (var_to_object( param[0] ) <> nil) then begin
      result  := var_to_object( param[0] ).ClassName;
    end;

  end;

end;
 

// IUnknown - it needs in Delphi 3 only
{$ifdef VER100}
function TForm1.QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;
begin
  if GetInterface(IID, Obj) then result := 0 else result := E_NOINTERFACE;
end;
function TForm1._AddRef: Integer; stdcall;
begin
  result := 0;
end;
function TForm1._Release: Integer; stdcall;
begin
  result := 0;
end;
{$endif}

end.

Using TStrings, TCollection, TDataSet

For some Delphi classes it creates automatically extended "ActiveX Shell" object for receiving access to basic features of Delphi object via OLE automation. 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 Special methods for getting value of element Special method for setting value of element
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 Special methods for getting value of element
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 Special methods for getting field and value of field, it correspond to properties


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.


Using with MS ActiveX Scripting, embedding scripts into the application

MS ActiveX Scripting is just one of ways to using application's object model created with "ActiveX Shell" technology. You can use with your application any other scripting engine that uses AciveX model. See above about it.

For embedding scripts on VBScript, JScript into an application in addition 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

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: use this function for checking availability of component by its GUID. It allows to do dinamic check that all 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:
 
    var this_form, scripting, module: Variant; 
begin

// creating an instance of MS Script Control
  scripting := CreateOLEObject('ScriptControl'); 
  scripting.AllowUI  := True; 
  scripting.Language := 'VBScript'; 
 

// creating "ActiveX Shell" for object, 
// here for the form given as Self reference
  this_form := obj_to_variant(Self); 

// The fist way is to add name to ScriptControl name space
// example of creation of reference 'this' that is global name for all scripts code
  scripting.AddObject('this', this_form, True); 

// example of use of the reference 'this'
  scripting.ExecuteStatement(
   'this.Caption = "New Caption 1"' + #13#10 + 
   'MsgBox(this.Caption)'
  ); 
 

// The second way is to use a special "module" for the object
// where this. prefix is not need.
  module := scripting.Modules.Add('my_form_module', this_form ); 

  module.ExecuteStatement(
    'Caption = "New Caption 2"' + #13#10 + 
    'MsgBox (Caption)'
  ); 

end;

In detail about using MS Script Control, its properties, methods and objects you can read in msscript.hlp from the MS ScriptControl distribution kit.
 

Using with HTML Template component to create HTML, XML, SGML reports
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.


Creating OCX library

This is only if you need to execute your application as OLE object.

For creating an OCX library with "YourAppName.Application" OLE object is used Delphi ActiveX framework:

  1. use Delphi menu > New  then choose ActiveX > ActiveX Library
  2. save project as "YourAppName"
  3. use Delphi menu > New  then choose ActiveX > Automation object
  4. write "Application" as Class Name
  5. add properties and methods in the Type Library editor and register library
Now you have OCX library with "YourAppName.Application" object inside.

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" 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() 
loop

MsgBox(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.
 

Professional version's advantages
I.  Multithread support is in professional version only.

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 destroy here the 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 anytime by using  var_to_object  function.
 

Source code buy online
For commercial purposes you have to buy the professional version's sources with the license.

Go to our Components Purchase Page and buy it online. Maximum per two days after receiving the payment we send you sources with comments by e-mail and make it available for downloading from our secure web site.

Contact information
Contact person: Karim Yusupov

Send your questions and comments to  mailto:apelseen@mail.com?Subject=About the ActiveX shell technology

Apelseen software website is http://www.apelseen.da.ru/
 

Useful Internet links
Version 1.9
Copyright (c) 1999, Apelseen software. All Rights Reserved