Advertisment

PCQ Labs Setup Component

author-image
PCQ Bureau
New Update

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

    Advertisment
  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. Advertisment
  5. Display a readme / documentation file on completion of the

    install process.
  6. Decompress files compressed with Microsoft’s

    COMPRESS.EXE applications.
  7. 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.

    Advertisment

    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.

    Advertisment

    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.

    Advertisment

    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.

    Advertisment

    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.

    Advertisment

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

      Advertisment