[WebBot HTML Markup Component]
[WebBot Component][WebBot HTML Markup Component]
[WebBot Component]
Writing
Team Development
Support Tools
for Delphi 2.0
A Presentation
to the
Australian Delphi User Group
by
20 May 1997
"The ease of managing a development project is reversely proportional to the number of developers employed" Old Chinese Proverb
What is a Team Development Support Tool?
A Team Development Support Tool (TDST) is a utility program which assists a team with the logistics of developing a computer system. Tasks such as the testing, debugging, and delivery of software, archiving and versioning of source code, and bug and enhancement logging become more complicated to coordinate as the size of a development team grows.
Team Development Support in Delphi
Far be it for me to say that Delphi has any weaknesses, but, if I had to identify one, then team support is probably Delphi's achille's heel. Whilst the Client/Server Suite comes with a customised version of PVCS (a source code version control system from InterSolv) and the Developer version has support for PVCS, neither offers much more to assist a team development effort. Fortunately, however, Borland have provided the means with which to create our own team development support tools and integrate them into Delphi's IDE. Unfortunately the interface (which Borland calls the Open Tools API) and how to use is poorly documented.
The purpose of my presentation tonight is provide a usable document describing Delphi's Open Tools API and hopefully, by proving that it is not difficult to do, encourage you to attempt to create your own tools and experts for Delphi.
Delphi's Open Tools API
Delphi 2.0's Open Tools API1 provides you with the opportunity of creating your own tools that work closely with Delphi's IDE. The eight units files which make up the Open Tools API (listed in Table 1) provide the means of interfacing with a variety of facilities in Delphi's IDE.
Table 1.
Units in the Open Tools API.| Unit Name |
Purpose |
| VirtIntf |
Defines the base TInterface class from which other interfaces are derived. Also defines TIStream class which is a wrapper around a VCL TStream. |
| IStreams |
Defines TIMemoryStream, TIFileStream, and TIVirtualStram classes which are descendants of TIStream. These interfaces can be used to hook into the IDEs own streaming mechanism. |
| ToolIntf |
Defines TIMenuItemIntf and TIMainMenuIntf classes which enable the Open Tools developer to create and modify menus in Delphi IDE. Defines the TIAddInNotifier class which allows add-in tools to be notified of certain events within the IDE. Most importantly, this unit defines the TIToolServices class which provides an interface into various portions of the Delphi IDE - such as the editor, component library, code editor, form designer, and file system. |
| VCSIntf |
Defines the TIVCSClient class which enables the Delphi IDE to communicate with version control software. |
| FileIntf |
Defines the TIVirtualFileSystem class which the Delphi IDE uses for filing. Experts, version control managers, and property and component editors can use this interface to hook into Delphis own file system and to perform special file operations. |
| EditIntf |
Defines classes necessary for manipulating the Delphi code editor and form designer. The TIEditReader class provides read access to an editor buffer. TIEditWriter provides write access to the same. TIEditView is defined as an individual view of an edit buffer. ITEditInterface is the base interface to the editor which can be used to obtain the previously mentioned editor interfaces. The TIComponentInterface class is an interface to an individual component sitting on the form at design-time. TIFormInterface is the base interface to a design-time form or data module. TIResourceEntry is an interface for the raw data in a projects resource (*.RES) file. The TIResourceFile is a higher-level interface to the project resource file. TIModuleNotifier is a class which provides notifications when various events occur for a particular module. TIModuleInterface is the interface for any file or module open in the IDE. |
| ExptIntf |
Defines the abstract TIExpert class from which all experts are descended. |
| DsgnIntf |
Defines the TFormDesigner, TPropertyEditor, and TComponentEditor classes which are used to create custom component property and component editors. |
The complete Open Tools API is available only with Delphi Developer and Client/Server Suite. Delphi Desktop contains only the units for creating component and property editors. You can find the source code for the Open Tools interfaces in the \Delphi 2.0\Source\ToolsAPI subdirectory2
A Simple Example
The following example which illustrates the steps necessary to implement add-in functionality via Delphi's Open Tools API is deliberately simplistic so as not to cloud the issue with technicalities.
The Steps
The basic steps involved in creating a Expert for Delphi are detailed below:
Decide whether expert will be Component Library or Dynamic Link Library based
| There is no hard and fast rule that dictates whther an expert should reside in the component library (DCL) or a DLL. The overriding consideration should be the size of the expert. Since the size of you DCL file (i.e. complib32.dcl) is generally well over a megabyte, you should try to avoid adding huge experts to the component library. The bigger the DCL gets, the longer it will take to rebuild when you add new components. | |
| Another consideration is the difference in the way the two different types of experts are loaded and unloaded from the IDE. Component library experts require the component library to be rebuilt, whereas DLL experts require a registry entry2, and Delphi must be exited and restarted for changes to take effect. |
Some additional notes about DLLs
| A DLL function or procedure to be called by external programs (or from within Delphi's IDE) must follow these guidelines: |
| Create a new source unit which references the relevant
Open Tools API units in its uses clause Decide and plan what your expert will do, this will help you determine what IDE services you wish to interface with (from Table 1). Add the relevant unit(s) to your unit's uses clause. | |
| Create a descendant of TIExpert and override the
appropriate methods TIExpert is a pure-virtual base class defining the expert interface to the Delphi IDE, and is defined as follows: |
type
TIExpert = class(TInterface)
public
function GetName: String; virtual; stdcall; abstract;
function GetAuthor: String; virtual; stdcall; abstract;
function GetComment: String; virtual; stdcall; abstract;
function GetPage: String; virtual; stdcall; abstract;
function GetGlyph: HICON; virtual; stdcall; abstract;
function GetStyle: TExpertStyle; virtual; stdcall; abstract;
function GetState: TExpertStyle;virtual; stdcall; abstract;
function GetIDString: String; virtual; stdcall; abstract;
function GetMenuText: String; virtual; stdcall; abstract;
procedure Execute; virtual; stdcall; abstract;
end;
You do not need to override all the methods of the base class, below is a table briefly detailing the purpose of the methods and when it is necessary to override them:
| GetName |
REQUIRED. This must return a unique descriptive name identifying the expert. |
|
| GetAuthor |
REQUIRED is style is esForm or esProject. This should return the "author" of this add-in/expert. This could be a person or company, for example. This value will be displayed in the Object Repository. |
|
| GetComment |
REQUIRED if style is esForm or esProject. This should return a 1 - 2 sentence describing the function of this expert. |
|
| GetPage |
REQUIRED if style is esForm or esProject. Should return short string indicating on which page in the repository this expert should be placed. NOTE: The user can still override this setting from the Tool|Repository dialog. |
|
| GetGlyph |
REQUIRED if style is esForm or esProject. This should return a handle to a icon to be displayed in the form or project list boxes or dialogs. Return 0 to display the default icon. |
|
| GetStyle |
REQUIRED. Returns one of four possible values: |
|
| esStandard |
Tells the IDE to treat the interface to this expert as a menu item on the Tools/Experts menu. |
|
| esForm |
Tells the IDE to treat this expert interface in a fashion similar to form templates. |
|
| esProject |
Tells the IDE to treat this interface in a fashion similar to project templates. |
|
| esAddIn |
Tells the IDE that this expert handles all its own interfacing to the IDE through the TIToolServices interface. |
|
| GetState |
REQUIRED. If the style is esStandard, esChecked will cause the menu to display a checkmark. NOTE: This function is called each time the expert is shown in a menu or listbox in order to determine how it should be displayed. |
|
| GetIDString |
REQUIRED. This ID string should be unique
to all experts that could be installed. By convention,
the format of the string is: CompanyName.ExpertFunction,
eg. Borland.WidgetExpert |
|
| GetMenuText |
REQURED if style is esStandard. This should return the actual text to display for the menu item. NOTE: This function is called each time the parent menu is pulled-down, so it is possible to provide context sensative text. |
|
| Execute |
REQUIRED if style is esStandard, esForm, or esProject. Called whenever this expert is invoked via the menu, form repository dialog, or project repository dialog. The style will determine how the expert was invoked. This procedure is never called if the style is esAddIn. |
|
|
|
||
| TExpertInitProc |
Defines the number and types of parameters passed to the single exported entry-point to the expert DLL. |
|
| ToolServices |
A pure-virtual class containing all the tool services provided by the IDE. |
|
| RegisterProc |
The function to call in order to register an expert. NOTE: This function is called once for each expert instance that the DLL wants to register with the IDE. |
|
| Terminate |
Set this parameter to point to a procedure that will be called immediately before the expert DLL is unloaded by the IDE. Leave nil, if not needed. |
|
| Write the expert's process code |
Program your Expert's process code taking care to ensure that any code which can potentially raise an exception is protected by a comprehensive try .. except block. Any normal ObjectPascal code is acceptable in your expert, you can even display forms and use all the components in Delphi's VCL, however, it is a good practice to avoid displaying any form in a modeless state (this will cause real problems if your expert resides in a DLL).
| Install expert into IDE and execute |
Install your expert in one of the following ways:
VCL based expert
The VCL will be recompiled and your expert will immediately become available when the VCL is automatically reloaded.
DLL based expert
The DLL expert will become available when Delphi loads
| Test your expert's functionality |
Test to make sure your expert works as expected and repeat any of the preceeding steps as necessary
Listing 1. Example DLL Expert Project File, Otexdll.dpr.
libraryOtexdll; uses ShareMem, ExptIntf, Otexex, SysUtils, Classes; exports InitExpert name ExpertEntryPoint resident; begin end. Listing 2. Example Dual-Mode (DCL/DLL) Expert Source File, Otexex.pas. unit Otexex; interface uses SysUtils, Windows, Dialogs, ExptIntf, ToolIntf; type TOpenToolsExpert = class(TIExpert) private MenuItem: TIMenuItemIntf; procedure OnClick(Sender: TIMenuItemIntf); procedure SetMenuItem; public constructor Create; destructor Destroy; override; function GetName: String; override; function GetStyle: TExpertStyle; override; function GetIDString: String; override; end; procedure HandleException; {$ifdef MAKE_DLL} function InitExpert(AToolServices: TIToolServices; RegisterProc: TExpertRegisterProc; var Terminate: TExpertTerminateProc): Boolean; stdcall; {$else} procedure Register; {$endif} implementation {$ifdef MAKE_DLL} uses Forms, Controls; {$endif} procedure HandleException; begin ToolServices.RaiseException(ReleaseException); end; constructor TOpenToolsExpert.Create; begin try SetMenuItem; except HandleException; end; end; destructor TOpenToolsExpert.Destroy; begin MenuItem.Free; inherited; end; function TOpenToolsExpert.GetName: String; begin Result := 'TOpenToolsExpert Expert'; end; function TopenToolsExpert.GetStyle: TExpertStyle; begin Result := esAddIn; end; function TOpenToolsExpert.GetIDString: String; begin Result := 'OzmoSys.TOpenToolsExpert'; end; procedure TOpenToolsExpert.SetMenuItem; var MainMenu: TIMainMenuIntf; Tools: TIMenuItemIntf; Flags: TIMenuFlags; begin {Make sure the menu item is only installed once } if MenuItem <> nil then Exit; { Add the menu item to the Tools menu } MainMenu := ToolServices.GetMainMenu; try Tools := MainMenu.FindMenuItem('ToolsMenu'); if Tools = nil then raise Exception.Create('Cannot locate Tools menu'); try Flags := [mfEnabled, mfVisible]; // Insert as first menu item. Appending as the last Tools menu // item does not work because when the user adds tools, they // overwrite the added item. MenuItem := Tools.InsertItem(0, {$ifdef MAKE_DLL} 'Open Tools DLL Expert Example' {$else} 'Open Tools DCL Expert Example' {$endif} , '', 'A simple test of the Open Tools API', 0, 0, 0, Flags, OnClick); finally Tools.Free; end; finally MainMenu.Free; end; end; procedure TOpenToolsExpert.OnClick(Sender: TIMenuItemIntf); begin try ShowMessage('I am here and doing it!'); except HandleException; end; end; {$ifdef MAKE_DLL} function InitExpert(AToolServices: TIToolServices; RegisterProc: TExpertRegisterProc; var Terminate: TExpertTerminateProc): Boolean; stdcall; begin Result := True; if ToolServices = nil then begin try ToolServices := AToolServices; Application.Handle := ToolServices.GetParentHandle; RegisterProc(TOpenToolsExpert.Create); Terminate := nil; except HandleException; end; end else Result := False; end; {$else} procedure Register; begin RegisterLibraryExpert(TOpenToolsExpert.Create); end; {$end if} end.
Version Control Systems
One tool no self-respecting developer should be without is a Version Control System (VCS)
A VCS (also somtimes referred to as a Configuration Management System or Source Code Librarian) is a utility program which stores all the various versions of a "source" file in a single, compact, annotated library. The "source" file is most commonly a text file containing program source code, but may also be a non-text (binary) file such as Delphi Form Files (.DFM) or compiled units (.DCU).
A VCS lets you quickly and easily "go back" to an old version in the event that the latest version of that file has a new bug (making code experimentation possible).
A VCS is an essential tool for any team development environment, providing the following benefits
This is one team development tool no developer (either individual or team) can afford not to have, ... I have lost count of the times my favourite VCS has saved my bacon.
Delphi provides a Version Control System interface in both the Developer and Client/Server versions (C/S is shipped with a customised version of PVCS from InterSolv).
TLib Source Code Version Control Expert for Delphi (an example)
I have used Tlib (from Burton System Software) for many years and prefer it to PVCS for a number of reasons (including its small footprint, ease of use, configurability) So, rather than settle for a VCS that I wasn't entirely confortable with, I decide to write my own Version Control Expert to enable access to TLib from within Delphi's IDE.
Delphi Library Configuration Manager (A Standalone Tool)
A tool does not necessarily have to integrate into Delphi's IDE, but can also be (and in some cases must be) a standalone utility.
One example of this type of standalone utility is a small program I developed which manages and simplifies switching between different versions of Delphi (i.e. Developer and Client/Server Suite) or between different DCLs, and probably between different 32-bit versions ie. Delphi 2.0 and 3.0 (although this has yet to be tested).
The reason DLCM must be standalone is to allow it to write saved configuration information back to the Windows Registry (something which cannot happen whilst the Delphi IDE is running, and in fact is prevented by DLCM). DLCM is a simple utility, but it effectively gets around Delphi's annoying problem of overwriting previous library configuration information stored in the Windows Registry.
Figure 1. DLCM in action.
Download DLCM (freeware).
Summary
Borland's Delphi is an outstanding programming environment combining the ease of drag-and-drop development with the control of source level code manipulation. Delphi's developers sensibly recognised that they could not forsee (or fully cater for) every development situation, they also realised that Team Development Support Tools (like Programmer's Editors) are a very personal issue; so, rather than give us the kitchen sink (and throw in the garbage bin) like a certain company we all love to hate, they provided the means with which to integrate our own tools into the IDE (via the Open Tools API). We should not miss the opportunity to improve the way we interact with Delphi. With very little effort, day to day tasks can be reduced to a click on a menu option, or your favourite coding tool can become part of your favourite development environment, ... thanks to Delphi's open architecture.
Reference Material/Suggested Reading
Contacting OzmoSys
WWW
http://werple.net.au/~ozmosys