by January 4, 1999 0 comments

To begin with (PCQ, Mar 96) we wrote a application
PCQSETUP.EXE that could read instructions from an INI file and install your application
files from distribution disks to the target machine. Lets recap the features of PCQSETUP.
It can

  1. Handle multiple groups of files, with each group going to a
    different directory on the target machine.
  2. Create a Program Group and place within it program items
    that call your application(s).
  3. For database applications based on the Borland Database
    Engine (BDE), it can create a BDE alias that your application may require on the target
    machine.
  4. Display a readme / documentation file on completion of the
    install process.
  5. Decompress files compressed with Microsoft’s
    COMPRESS.EXE applications.

The only shortcoming with PCQSETUP was that you had to
write the INI file manually. An easy enough task for small applications with a limited
number of files. However, for a large application with scores of files, the task of
creating the distribution disks and creating an .INI file could prove daunting. So in our
next episode of RAD (Apr 96) we wrote the PCQEXP to give you an easy visual interface to
create an INI file for use with PCQSETUP.

What we plan to do this month is to write PCQSETUP, our
install utility, as a Delphi component. As a component, every time you need to write an
install application, you can drop the component on to a form, customize it to accommodate
any special install requirements and have your install program ready within minutes. You
can even develop the component further and distribute to others and make some money.

Now component creation is not a subject that we can pretend
to teach through these columns. It”s too vast and it is very technical. In this column we
will only discuss how to write the setup utility as a component. You will be able to
benefit from what follows only if you have gone through the Component Writer”s Guide that
ships with Delphi and have attempted creating simple components. Fortunately, the
component that we will create is a relatively simple one and we will need to focus on a
very narrow area of component creation.

If you aren”t geared up for component writing, but are a
Delphi programmer, you could still take advantage of this month’s column by
downloading the component from PCQ Online and making use of it. If you are not a Delphi
programmer, I am afraid this episode of RAD will be of little use to you.

Advantages Of Writing
PCQSETUP As A Component

Applications distributed as executables are written for end
users. Components, on the other hand, are written for developers. An application cannot be
customized, but a component can. It makes a lot of sense to write PCQSETUP as a component
since it is a utility that targets developers with a need to distribute applications.
Developers write applications that vary extensively in size and hue. It is unlikely that a
standard install routine will meet all their requirements.

Currently, PCQSETUP does what it is designed to do and
nothing more. (Nothing less either, we hope.) It provides just the core functionality
common to most install routines. But these often need to perform additional tasks such as
displaying an opening welcome screen, confirming that adequate diskspace is available, or
unzipping files compressed with algorithms other than what PCQSETUP can handle. It does
not make sense to add these features to PCQSETUP and in the process inflate its code size.
Not all the features will be used in all install routines. However, as a developer you
will want to retain the option to customize the install process to suite your special
requirement.

Before the advent of software components, it was not
possible to customize any application unless you had its code. With components, even when
you do not have access to the source code you can customize an application to do precisely
what you want and the way you want it. This is possible because the component writer
selectively exposes the internals of the application through properties and events.
Properties are internal variables that the component publishes”. By publishing an internal
variable, the component lets the user set its value at design time. The values that you
assign to a component property is internally stored in a private field or variable of the
component.

Properties, or ‘published’ variables, are
distinct from public component variables. The public component variables are also
accessible to the developer, but only at run time.

A component user can customize a component by either
assigning values to, or changing the values of its properties. For example, the component
that we will create will have a property called IniFileName. Since this is a property, the
developer can specify the name of the .INI file that the setup program must read at design
time. Contrast this with the PCQSETUP application, where the name of the
.INI file is hard
coded into the application. An alternative implementation, using a public variable, would
have been to prompt the user for the name of the .INI file at run time. I won’t seek
you opinion on how elegant a solution that would be.

Events provide an even more important mechanism for
customizing components. The application developer can customize the working of components
by writing event handling code. An event serves as a mechanism that links an occurrence
within the code of the component to the code written by the application developer. Events
may be triggered by the some action on the part of the user. Such events are referred to
as external events. For example, if the user clicks the mouse button, it may trigger the
OnClick event of a component. Events may also be triggered by code within the components,
in which case they are referred to as internal events.

In the component that we are about to write we would be
dealing only with internal events. For example, the code within the component will trigger
an event OnFileCopy after copying a file to the target machine.

Whenever an event occurs, the code within the components
looks for the event handler. The event handler is written by the developer. If the
component code finds an event handler, it executes the code within it before getting on
with its job. You can look upon events as methods in reverse. Methods represent component
code that developers can call from the code that they write. An event handler, on the
other hand, is a block of developer’s code that the component code calls in response
to an internal or external triggering.

The PcqSetup Component

The setup component that we are about to write is a non
visual component so we will derive it from the TComponent class of Delphi. It will have
just one property–IniFileName. The component user will assign to it the name of the
.INI file that the setup application should read instructions from. The
.INI file, as we
have already discussed, can be created either manually or by using
PCQEXP.

To allow for customization
the component will have four events

The OnBeginInstall event will occur before the setup
program starts reading the setup .INI file. Typically an event for this event will check
on disk space availability, or display a copyright agreement. The OnFileCopied event will
occur every time the setup program finishes copying a file to the target machine. An event
handler for this event maybe used to unzip/explode a file compressed with a program other
than COMPRESS.EXE. The OnMissingFile event will occur anytime the setup program does not
find a file that it is required to install. The component has a built in default behavior
to handle this contingency–It informs users about the missing file and lets them
specify a new source directory for the install files. An event handler will let you
override the default handling. Finally, the OnFinishedInstall event will occur after the
installation is completed. On completion of installation you may like to run the
application that they have just installed. You could write an event handler for this event
to do just that. However, you would need to keep track of the directory to which the main
application file was installed, since the user could have changed the target directory.
For this you would also need to write an event handler for the OnCopyFile event that would
store the directory to which the main application file was copied. This event handler
receives the name of the file just copied and the directory to which it was copied.

Events–Getting
Technical

We have seen that an event is a mechanism to link the code
within a component to the code written by an application programmer. We will now see how
this is done.

In Delphi, events are implemented in a manner similar to
properties. In fact, events are properties too. An event property differs from a data
property in one respect. The data properties, and the private variable that they
represent, have a data type — an Integer or String. In the case of event properties the
private variables that they publish are invariably method pointers. In other words rather
than pointing to data these pointer point to code that the developer has written.

A developer can customize a component by changing the value
of a data property. Similarly, a developer can also customize a component by write event
handling code for event properties.

When you define an event you go through the following steps

  1. Define the event handler type–This probably sounds a
    lot worse than it actually is. We said earlier that events are method pointers. Now a
    method call may involve passing parameters. The number of parameters and the parameter
    types could vary from method to method. Before we can declare a pointer to a method we
    need to define the type of method it points to.
  2. Declare the event–Once you have defined the type of
    method your event points to you are ready to declare the event.
  3. Trigger the event–You can trigger an event in response to a
    user input, or you can trigger it on state change. In our install component, all the
    events are triggered by state changes – Installation starts, File is copied etc.
  4. Calling the event–Calls to the event handler need to be
    made from one central point in the component rather than from several places. The usual
    practice is to declare a virtual method in your component that calls the user’s event
    handler and provides any default handling. If you do that, another developer can create a
    new component by inheriting from your component and change its default handling of an
    event by overriding the virtual method in your component.

Let”s look at some code snippets. I am sure it will lighten
things up and clear the theoretical cobwebs! First, we will see how we implement the
IniFileName property. Here are the relevant portions from the class definition.

In the private section of the component class,
TleInst, we
declare an object field FIniFileName of type String. In the published section we declare
the property IniFileName of type String. The ‘read FIniFileName’ in the
declaration specifies that to read the property value the component directly access the
internal storage field. However, when you assign a value to the property a private method
is used to write the value to the storage field. This mechanism enables the component to
update its status.

A look at the implementation of the private method should
illustrate this Implementation.

Before writing the new property value to the storage field,
we check if the value has changed. If it has we ensure that the INI file actually exists.
If it does, we free the existing TIniFile Object, extract the file path from the INI
filename and create a new TIniFile object. Only after updating the component status do we
assign the new name value to the storage field.

Implementing Events

We now study code snippets to implement event properties.
We will first present the relevant parts of the interface section to let you get the
complete picture, and then focus on parts of the code.

Recall the steps in defining events. Step one is to define
the event handler types

The event handler for the OnBeginInstall event receives a
single Boolean parameter–Cancel of type var. We define a pointer to the method as
TBeginInstallEvent. Since the parameter is of type var the user can change its value. The
default event handling code within the component examines the value of Cancel after
executing the event handler. If the value has been set to false the component aborts the
installation by raising an exception. For example, if the event handler finds that the
existing diskspace is inadequate for the installation it can set the Cancel parameter to
True.

We define event handler types for the other two events in a
similar fashion. (We will let you figure out the explanation.) For the OnFinished we use
the default event handler type which is of type TNotifyEvent. The event handler in this
case just receives a pointer to the component that originates the event. (You may like to
look up the on line help for more on the TNotifyEvent)

Moving on to step two for defining an event — declare the
events as properties with corresponding storage fields. The event properties are declared
as follows in the published part of the class definition

Notice how we declare the event properties as of
TBeginInstallEvent, TMissingFileEvent, etc. The properties have their corresponding
internal storage fields that store pointers. These are declared in the private section as

Step three of defining an event is to setup a mechanism to
trigger the event. We centralize calls to the event handlers through virtual methods
defined in the protected part of the class definition as follows

Wherever in the code we need to trigger an event we place a
call to the virtual method corresponding to the event. This code snippet from the Execute
method of the component illustrates this

Before, the component reads the INI file, it triggers the
OnBeginInstall event with a call the BeginInstall method. The call is made with the Cancel
parameter set to False.

The final step in defining events is to setup the call to
the event handler. The BeginInstall method performs the task of looking for, and where
assigned, executing the event handling code. Have a look at its code.

The code checks to see if an event handler has been
assigned to the OnBeginInstall event. If it has, it calls the assigned event handler with
the Cancel parameter set to False. After executing the event handler it examines the value
of the Cancel parameter. If it has been set to False the default handling is to raise an
exception.

We will now see how the other three events are triggered
and called.

OnMissingFile and
OnFileCopied Events

The OnMissingFile and OnFileCopied events are triggered in
the InstallFile method of the component. Here is the code for the InstallFile method.

OnMissing and OnFileCopied
Events

The OnMissingFile event is of TMissingFileEvent type and
takes the following parameters

If you go back to the declaration of TMissingFile event you
will notice that the source and cancel parameters are of type var. The event handler can
allow the user to either point to a different source directory or cancel the installation.
Here is the code for the MissingFile method.

Notice how the MissingFile method calls the install file
method again if the user says — Retry. This nesting can get chancy and easily blow your
stack. We keep track of the number of retries with the Inc(FailedCopy) part of the code.
After two bad retries the InstallFile method raises an exception, nevertheless.

Let us now look at the code for the FileCopied method.

The event handler for this event is called with the name of
the file that has just been installed and the directory in which it has been installed.
This lets the developer write code in the event handler to say unzip the file if it has
been compressed using an algorithm that the install program cannot handle.

The OnFinishedInstall Event

The OnFinishedInstall event is triggered with this call in
the Execute method of the component.

Conclusion

I am afraid we have space for just this much and no more.
We have barely managed to focus on creating events in components. We haven’t even had
time for an overview of component creation. As mentioned at the start, the complete source
code for the component is available for download from the DEV forum of PCQ Online as
STUPCOMP.ZIP. Those of you who do not have access to PCQ Online need not despair –
you will be able to get the complete source code on the Summer 96 CD-ROM to be distributed
free along with copies of the June issue of PC Quest.

With this episode we conclude the three part series in
which we have created a suite of applications to relieve you of your installation
headaches. We hope you have enjoyed the series and found it useful. We would love to hear
from you in either case. The feedback will allow us to better tune and focus the
forthcoming episodes.

The virtual method that makes the actual call is also
straightforward.

procedure TleInst.Execute;
var
.
.
.
begin
.
.
.
{Finished copying files. Set up program group}
    CreatePgmGroup
(IniFile);
    Wait(500);
    CreateAlias;
    FinishedInstall;
    ViewDoc;
.
.
.
end;
procedure TleInst.FinishedInstall;
begin
  if Assigned(FOnFinishedInstall) then
    FOnFinishedInstall(Self);
end;

 

 

 

 

 

procedure
TleInst.FileCopied(FileName, Destination: TFileName);
begin
  if Assigned(FOnFileCopied) then
    FOnFileCopied(FileName, Destination);
end;

 

procedure TleInst.MissingFile(FileName, DestName: TFileName; var Source: TFileName;
  var Cancel: Boolean);
var
  DiskNo: LongInt;
begin
  Inc(FailedCopy);
  {Check for planned disk changes here}
  DiskNo := IniFile.ReadInteger(‘Disk Change’, FileName, 0);
  if DiskNo <> 0 then
  begin
    if not InputQuery(‘Setup requires Disk ’ + IntToStr(DiskNo), ‘SourcePath’, Source) then
      raise Exception.Create(UserCancel);
    if Copy(Source, Length(Source), 1) <> ‘\’ then
      Source := Source + ‘\’;
    InstallFile(FileName, DestName);
    Exit;
  end ;
  {No disk change was planned here}
  if Assigned(FOnMissingFile) then
  begin
    FOnMissingFile(FileName, DestName, Source, Cancel);
    if not Cancel then
      InstallFile(FileName, DestName)
    else
      raise Exception.Create(UserCancel);
  end
  else if InputQuery(File  + Source + FileName +  is missing. Retry?, `Source'',
      Source) then
    begin
      if Copy(Source, Length(Source), 1) <> \ then
        Source := Source + `\'';
      InstallFile(FileName, DestName);
    end
  else
      raise Exception.Create(FileReadError + FileName);
end;
OnBeginInstall
OnFileCopied
OnMissingFile
OnFinishedInstall

 

 

type
  TleInst = class(TComponent)
  private
    .
    .
    .
    FIniFileName: String;
    procedure InstallFile(FileName, DestName: TFileName);
   
    .
    .
    .
    protected
     .
     .
     .
    published
      property IniFileName: String read FIniFileName write
SetIniFileName;
      .
      .
      .
    end;
.. procedure TleInst.SetIniFileName(FileName: String);begin if FileName <> then begin if not 
FileExists(FileName) then raise Exception.Create(‘INI file ‘ + FileName + ‘ not found!’); if IniFile <> 
nil then IniFile.Free; SourcePath := ExtractFilePath(Application.ExeName); IniFile := TIniFile.
Create(SourcePath + FileName); end; FIniFileName := FileName;end;
type
  TBeginInstallEvent = procedure (var Cancel: Boolean) of object;
  TMissingFileEvent = procedure( FileName, Destination: TFileName; var
    Source: TFileName; var Cancel: Boolean) of object;
  TFileCopiedEvent = procedure (FileName, Destination: TFileName) of object;
type
  TleInst = class(TComponent)
  private
    .
    .
    .
    FOnBeginInstall: TBeginInstallEvent;
    FOnFileCopied: TFileCopiedEvent;
    FOnMissingFile: TMissingFileEvent;
    FOnFinishedInstall: TNotifyEvent;
    .
    .
    .
protected
    procedure BeginInstall(var Cancel: Boolean); virtual;
    procedure MissingFile(FileName, DestName: TFileName; var Source:
TFileName;
      var Cancel: Boolean); virtual;
    procedure FileCopied(FileName, Destination: TFileName); virtual;
    procedure FinishedInstall; virtual;
  public
    .
    .
    .
published
    .
    .
    .
    property OnBeginInstall: TBeginInstallEvent read FOnBeginInstall write
FOnBeginInstall;
    property OnMissingFile: TMissingFileEvent read FOnMissingFile write
FOnMissingFile;
    property OnFileCopied: TFileCopiedEvent read FOnFileCopied write
FOnFileCopied;
    property OnFinishedInstall: TNotifyEvent read FOnFinishedInstall write
FOnFinishedInstall;
  end;
type
  TBeginInstallEvent = procedure (var Cancel: Boolean) of object;
  TMissingFileEvent = procedure( FileName, Destination: TFileName; var
    Source: TFileName; var Cancel: Boolean) of object;
  TFileCopiedEvent = procedure (FileName, Destination: TFileName) of object;
property OnBeginInstall: TBeginInstallEvent
read FOnBeginInstall write FOnBeginInstall;
    property OnMissingFile: TMissingFileEvent read FOnMissingFile write
FOnMissingFile;
    property OnFileCopied: TFileCopiedEvent read FOnFileCopied write
FOnFileCopied;
    property OnFinishedInstall: TNotifyEvent read FOnFinishedInstall write
FOnFinishedInstall;
FOnBeginInstall: TBeginInstallEvent;
    FOnFileCopied: TFileCopiedEvent;
    FOnMissingFile: TMissingFileEvent;
    FOnFinishedInstall: TNotifyEvent;
procedure BeginInstall(var Cancel: Boolean);
virtual;
    procedure MissingFile(FileName, DestName: TFileName; var Source:
TFileName;
      var Cancel: Boolean); virtual;
    procedure FileCopied(FileName, Destination: TFileName); virtual;
    procedure FinishedInstall; virtual;
procedure TleInst.Execute;
var
  Group: TGroup;
  I, J: Integer;
  DestDir: TFileName;
  Cancel: Boolean;
begin
  try
    {The installation starts}
    Cancel := False;
    BeginInstall(Cancel);
    TotFiles := 0;
    FileCount := 0;
    IniFile.ReadSection(PgmSection, Groups);
    .
    .
    .
procedure TleInst.BeginInstall(var Cancel:
Boolean);
begin
  if Assigned(FOnBeginInstall) then
  begin
    FOnBeginInstall(Cancel);
    if Cancel then
      raise Exception.Create(UserCancel);
  end;
end;

 

 

 

 

 

procedure TleInst.InstallFile(FileName,
DestName: TFileName);
var
  SrcFile : Integer;
  DestFile : Integer;
  RetCode : Longint;
  OpenFileBuf   : TOFStruct;
  SrcFileName,
  DestFileName : array[ 0..255 ] of Char;
  Cancel: Boolean;
begin
  if not FileExists(SourcePath + FileName) then
  begin
    {We don’t want the program going into the weeds with no stack
space!!}
    if FailedCopy > 2 then
      raise Exception.Create(FileReadError + FileName);
    Cancel := False;
    MissingFile(FileName, DestName, SourcePath, Cancel);
    Exit;
  end;
  StrPCopy( SrcFileName, SourcePath + FileName );
  if GetExpandedName(SrcFileName, DestFileName) < 0 then
    StrPCopy(DestFileName, DestName + ‘\’ + FileName)
  else
    StrPCopy(DestFileName, DestName + ‘\’ +
ExtractFileName(StrPas(DestFileName)));
SrcFile := LZOpenFile( SrcFileName, OpenFileBuf, of_Read );
  try
    DestFile := LZOpenFile( DestFileName, OpenFileBuf, of_Create );
    try
      RetCode := LZCopy( SrcFile, DestFile );
      case RetCode of
        LZERROR_UNKNOWNALG:
          raise Exception.Create(‘The
source file was compressed with an unknown algorithm’);
        LZERROR_WRITE:
          raise Exception.Create(‘The
destination drive is full!’);
        else if RetCode < 0 then
          raise Exception.Create(‘Could
not copy file!’);
      end;
    finally
      LZClose(DestFile);
    end;
  finally
    LZClose(SrcFile);
  end;
  FileCopied(FileName, DestName);
end;
FileName – The name of the file being
installed
DestName – The target directory
SourcePath – The source directory for the file
Cancel – A boolean

 

 

 

 

 

No Comments so far

Jump into a conversation

No Comments Yet!

You can be the one to start a conversation.

<