Marco's Web Center

Delphi Developers' Handbook

Chapter 15: Other Delphi Extensions

Copyright Marco Cantu' 1997

The Version Control System Interface

A version control system (VCS for short) is a tool that can help several programmers work on a single project in an organized manner. Typically such a tool keeps track of who changed a file, and what changes occurred within each version, and it prevents two programmers from modifying a given file at the same time. The Client/Server Suite Edition of Delphi ships with a limited version of Intersolv’s PVCS source control software. Other tools are available, such as MKS’s Source Integrity and the shareware ViCiouS originally developed by Bob Swart and now published by SureHand Software.

Professional version control systems are typically built upon the concept of storing the various files and revisions in a database. Some systems save just the changes to a file (such as added or deleted text), while others save the full content of the previous versions. This makes it possible to build several versions of a project with a given set of source files, but it also allows programmers toenables rolling back changes to a more stable version in case of problems. More advanced systems can track different sets of changes and can integrate them.

Delphi’s version control system interface is something of an alternative to the expert/wizard interface, but it works in a distinctive way, using the the VCS Manager DLL. This results in two subtle differences between a Delphi VCS and a Delphi wizard. The first is that you can install only one VCS at a time, while you can install many wizardexperts. Second, a Delphi VCS must reside in a DLL (you can’t create a DCU-based VCS).

You can specify a preferred VCS by setting a Registry entry that identifies the filename of an appropriate DLL:

HKEY_CURRENT_USER\Software\Borland\Delphi\3.0\Version Control\VCSManager

A more visible difference is that a VCS displays its own a pull-down menu, which can contain many items. This is much easier than adding menu items via one or more add-in wizards.

The TIVCSClient Interface

The ToolsAPI’s VcsIntf unit defines the base class for Delphi’s standard version control system interface. By default, this unit isn’t part of the Delphi library, so you’ll need to copy it into your project directory (and add it to the uses clause of the main form’s source file). Here is the class declaration (which resembles the component editor interface class because of the GetVerbCount, GetVerb, and ExecuteVerb methods):

type
  TIVCSClient= class(TInterface)
    function GetIDString: string; virtual; stdcall; abstract;
    procedure ExecuteVerb(Index: Integer); virtual; 
      stdcall; abstract;
    function GetMenuName: string; virtual; stdcall; abstract;
    function GetVerb(Index: Integer): string; virtual; 
      stdcall; abstract;
    function GetVerbCount: Integer; virtual; stdcall; abstract;
    function GetVerbState(Index: Integer): Word; virtual; 
      stdcall; abstract;
    procedure ProjectChange; virtual; stdcall; abstract;
  end;

This declares an abstract interface class, which is necessary whenever you implement a class in an external DLL. Accordingly, you simply build a DLL to create your own version control system. However, before we build a VCS, let’s examine the TIVCSClient class’s methods:

  • GetIDString: This method returns a unique identifier for the VCS client. We suggest using your company name followed by the VCS name.
  • GetMenuName: This method allows a VCS to specify its menu caption. This caption identifies a menu which will contain the Delphi VCS menu items. If there is no VCS menu, we simply return a new string. (If the menu name is empty, Delphi won’t execute the three following methods).
  • GetVerbCount: This method returns the number of menu items in the VCS menu. If you return a blank string, Delphi creates a separator in the VCS menu.
  • GetVerbState: This method returns the state of a verb (menu item) as either vsEnabled or vsChecked. However, you’ll notice that the data type is a word (not a specific set type), and that these two values are constants, not enumerated values. To return both flags at the same time you need to add their values.
  • ExecuteVerb: This method executes the action that corresponds to a given verb (determined by the Index parameter), when the user chooses a given menu item.
  • ProjectChange: Delphi calls this method whenever the state of the current project changes. This happens when the user opens or closes a project, and when the user adds a unit to the project or removes one from it. Creating a new project calls this method twice, since Delphi closes the current project before opening the new project. Delphi calls this method after changing the state of the project. For instance, if the user opens a project, Delphi calls this method when the project is active, but prior to opening any windows.

As with DLL wizards, to install a VCS you have to define a specific entry-point function in the DLL. Within this function you need to return a pointer to the newly created TIVCSClient object. This time, however, there is no need to call a registration function (since you can have only one VCS at a time). Here is the declaration of the entry-point function:

TVCSManagerInitProc = function (
  VCSInterface: TIToolServices): TIVCSClient stdcall;

When Delphi calls this function, it passes the global ToolServices object as the only parameter.

Project Information in a VCS

We can now start building our own VCS. Actually, we’ll start using the VCS interface to build a tool that is not a true VCS. The code will be quite simple, but we’ll still be able to create a program that does something meaningful.

In this last chapter, we built a project information wizard. The main form of our simple VCS tool is the same, and it displays the same information. However, here the form is modeless, so it can remain open while we work on the project. The VCS will receive notification for any change to the project, so we’ll be able to update the window appropriately.

Here is the initial part of the DLL’s source code. In the uses statement, you’ll notice that we refer to the VcsIntf unit. However this unit isn’t normally part of the VCL or standard libraries (and therefore isn’t in the normal path), so you should add its path (‘C:\Program Files\Borland\Delphi 3\Source\ToolsAPI’ in the default installation) to the Search Path in the Project Options dialog box.

  • The three VCS sample programs include the Delphi default path for the ToolsAPI source code directory. If you’ve installed the environment on a different drive or in a different directory (or if you are using a different version of Delphi), you should update the Search Path project option accordingly in order to recompile these programs.
library VcsPrj;
 
uses
  Sharemem, SysUtils, Classes, ExptIntf, Forms,
  ToolIntf, Dialogs, Windows, VcsIntf, VirtIntf,
  PrjForm in 'PrjForm.pas' {PrjInfoForm};
 
type
  TDdhProjectVcs = class (TIVCSClient)
  public
    function GetIDString: string; override;
    procedure ExecuteVerb(Index: Integer); override;
    function GetMenuName: string; override;
    function GetVerb(Index: Integer): string; override;
    function GetVerbCount: Integer; override;
    function GetVerbState(Index: Integer): Word; override;
    procedure ProjectChange; override;
    destructor Destroy; override;
  end;

Next comes the source code of the various methods. Most of these methods are quite simple, and require no further explanation. The only moderately complex code is in the GetVerbState and ExecuteVerb methods. GetVerbState will enable the VCS menu items only if a project is active (except for the About menu item, which is always enabled ). The ExecuteVerb method updates and displays the VCS form, which contains the project information.

function TDdhProjectVcs.GetIDString: string;
begin
  Result := 'DDHandbook.DDHProjectVCS';
end;
 
function TDdhProjectVcs.GetMenuName: string;
begin
  Result := 'DHH Project VCS';
end;
 
function TDdhProjectVcs.GetVerbCount: Integer;
begin
  Result := 3;
end;
 
function TDdhProjectVcs.GetVerb(Index: Integer): string;
begin
  case Index of
    0: Result := '&Project Information...';
    1: Result := ''; // separator
    2: Result := '&About DDH Project VCS...';
  end;
end;
 
function TDdhProjectVcs.GetVerbState(Index: Integer): Word;
begin
  // disable all but the last if no project is active
  if (ToolServices.GetProjectName <> '') or
      (Index = GetVerbCount - 1) then
    Result := vsEnabled
  else
    Result := 0;
end;
 
procedure TDdhProjectVcs.ExecuteVerb(Index: Integer);
begin
  case Index of
    0: try
        PrjInfoForm.UpdateTree;
        PrjInfoForm.Show;
      except
        ToolServices.RaiseException(ReleaseException);
      end;
    21: MessageDlg ('DDH Project Version Control System'#13#13 +
      'From the "Delphi Developer''s Handbook"'#13 +
      'by Marco Cantù and Tim Gooch',
      mtInformation, [mbOK], 0);
  end;
end;
 
procedure TDdhProjectVcs.ProjectChange;
begin
  PrjInfoForm.UpdateTree;
  {Application.MessageBox (PChar(
    'The project ' + ToolServices.GetProjectName +
    ' is changing'), 'DDH Project VCS', mb_OK);}
end;

The last method of the class, ProjectChange, simply calls the form’s UpdateTree method to update the output of the project information form. To help you see when Delphi is callings this method, we also display a message box (we’ve disabled this code in the disk version). Unfortunately, this forces the user to close this message box fairly often (since the event occurs frequently).

Studying the code above, you might wonder when Delphi creates the project information form, and when it destroys the form. We could have used a constructor and a destructor of the VCS class to illustrate this, but since we already have a VCS initialization function, we have used it to perform the form’s creation. However, we’ve created also a destructor to perform the form’s destruction. Here is the code of the destructor and of the initialization function:

destructor TDDHDemoVCS.Destroy;
begin
  PrjInfoForm.Free;
end;
 
function VCSInit (VCSInterface: TIToolServices): TIVCSClient; stdcall;
begin
  ToolServices := VCSInterface;
  Application.Handle := ToolServices.GetParentHandle;
  PrjInfoForm := TPrjInfoForm.Create (Application);
  Result := TDdhProjectVcs.Create;
end;
 
exports
  VCSInit name VCSManagerEntryPoint;

By the way, you’ll notice that before returning the new VCS client object, the VCSInit function stores the ToolServices object and the handle of the Application object in the proper global variables, and then creates the output form.

  • Be sure to notice the exports statement at the very end of the listing, which gives the entry function a specific, internal name. Delphi will use this name to locate and call the initialization function when you install this VCS DLL.

Now we’re ready to install our new VCS into Delphi. To do so, simply launch RegEdit in Windows 95 or RegEdt32 in Windows NT, locate the Delphi 3.0 section, and edit or add the Version Control key, as specified earlier and shown in Figure 15.3. Then, exit from Delphi (if it is open) and load it again to see the effect of the changes. You’ll see a new menu that contains a few items, as shown in Figure 15.4.

Figure 15.3: The information added to the Windows Registry to activate the new VCS DLL in the Delphi environment.
Figure 15.3
Figure 15.4: The new pull-down menu added by the VcsPrj DLL to Delphi’s own main menu.
Figure 15.4

If you want to test your VCS and modify it, we strongly suggest you copy and rename the DLL first (using a name such as VcsPrj1.DLL), and then reinstall the DLL under this new filename. Using this approach, you’ll be able to compile the DLL. Windows won’t let the Delphi compiler overwrite a DLL that’s currently in use by Delphi itself! Using the same filename, you cannot simply update the VCS DLL without uninstalling and reinstalling it. (This is the same problem we have with DLL-based wizards.) In contrast, by using a different filename, you can edit and recompile the DLL, exit from Delphi, delete the older DLL, rename the newly compiled one, and finally launch Delphi again.

[Chapter 15 Index]