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

      Stay connected with us through our social media channels for the latest updates and news!

      Follow us: