OP Tech

Delphi 1 / Delphi 2

 

By Dan Miser

 

Component Creation

Construction Guidelines for VCL Components

 

Components are the essence of Delphi. A well written and well organized component can aid Delphi programmers of all levels. Conversely, a poorly written component can keep any programmer from being productive. This article outlines some simple steps to help ensure your components end up being put to use.

 

File Organization

The first thing a component user must do when they receive a component is install it to the Component palette. There are three choices for distribution:

§        all .PAS files;

§        a .PAS registration unit, with supporting .DCU files; or

§        all .DCU files.

 

If there is some private interest that needs to be protected, .DCU files appear to be an attractive option. After all, the component user can’t look at the component’s source code if it isn’t distributed. In addition, if the Register procedure of the component is located inside the .DCU, the component user won’t have the power to change on which page the component is installed. This defeats the purpose of Delphi’s open and configurable environment.

 

Basic Delphi etiquette requires a component writer to distribute the registration unit as a .PAS file. Supporting .DCU files can also be distributed, if necessary. This allows the component user to install the component on any Component palette page. However, Borland’s .DCU format is subject to change (and has changed frequently over the years). A change in the .DCU format from Borland can mean incompatibility for all .DCU files not created with the same version of Delphi (sometimes even minor versions). This means that a component user must have faith that they can obtain an updated .DCU that can match the .DCU format-du-jour. The moral of this story is that a component user must be offered some way to obtain source code for all of the component’s files. For these reasons, many users don’t even consider employing a .DCU component solution.

 

In addition, if the component has any special property editors or component editors, they should be placed in the same unit as the Register procedure. This is how Borland distributes their VCL components. For example, looking at \DELPHI\LIB\STDREG.PAS will reveal several property editors and a component editor in the same file that contains registration routines for the standard components.

 

However, if the property editor contains a form, the property editor declaration should reside in that form’s unit. By placing the declaration there, Delphi will only add the overhead of the property editor’s form to COMPLIB.DCL. Because the property editor’s form will never be referenced by an application, either directly or indirectly, the property editor will not be compiled into the final .EXE.

 

Tying a Component to a DLL

There are two ways to access a DLL function: static import and dynamic import. Using the static import method requires that the DLL file name be known at compile time. In addition, after the function is imported from a DLL via static importing, that function must exist in that DLL. If the DLL cannot be found, Windows won’t let the process run.

 

Dynamic importing requires more work, but the payoff is tremendous: The programmer can recover gracefully from an error while trying to load a DLL. In addition, the name of the DLL doesn’t need to be known at compile time, thus adding flexibility to the design.

 

When writing a component wrapper around a DLL, the process responsible for loading the DLL is Delphi. This is because when the component was installed in Delphi, it was placed in Delphi’s component library, COMPLIB.DCL. If the link to the DLL is static and the DLL doesn’t exist, COMPLIB.DCL will render Delphi into a useless state (see Figure 1). To correct the error, the user must know what caused it. Even then, the user has no way of knowing the name of the offending DLL, or if this error was caused by only one missing DLL. Instead, the usual course of action is to copy a backup version of COMPLIB.DCL to the \Delphi\Bin directory.

 


Figure 1: Delphi fails to load COMPLIB.DCL because of bad DLL linking.

 

The best way to ensure this problem does not occur is to use dynamic importing of the DLL functions. This requires a call to LoadLibrary, followed by a call to GetProcAddress, for each and every function that must be called in the component (see Figure 2). The call to AddExitProc in the unit’s initialization section is included for compatibility with Delphi 1 and 2. In Delphi 3, Borland recommends the finalization section be used instead of AddExitProc for exit behaviors such as this.

 

 

implementation

 

const

  DLLName = 'GOOD.DLL';

  DLLProcedure1 : procedure = nil;

var

  DLLHandle : THandle;

 

function LoadDLL : Boolean;

var

  OldFlag: Word;

begin

  Result := False;

 

  { Turn off system error message }

  OldFlag := SetErrorMode(SEM_NOOPENFILEERRORBOX);

  DLLHandle := LoadLibrary(DLLName);

 

  { Restore system error messages }

  SetErrorMode(OldFlag);

 

  {$IFDEF WIN32}

  if DLLHandle = 0 then

  {$ELSE}

  if DLLHandle <= HINSTANCE_ERROR then

  {$ENDIF}

    begin

      ShowMessage('Cannot find file: '+DLLName);

      Exit;

    end;

 

  @DLLProcedure1 := GetProcAddress(DLLHandle, 'DLLProcedure1');

  Result := True;

 

end;

 

procedure UnloadDLL; far;

begin

  FreeLibrary(DLLHandle);

end;

 

initialization

 

  if LoadDLL then

    AddExitProc(UnloadDLL);

 

end.

Figure 2: A dynamic import.

 

By calling LoadLibrary, the error checking can now occur at the component level. This provides more control than the error checking and reporting done by Windows. In Figure 2, the component checks to make sure the DLL was loaded successfully. If it wasn’t, a message is shown explaining the problem. (Note that if an exception is raised here, Delphi will still be unable to load the component library.) Next, the functions from the DLL are imported using GetProcAddress. If there is an error importing these functions, the result of the GetProcAddress call will be nil. Otherwise, the result will be a valid address which your component will use to call the DLL function. This is more sophisticated than letting Delphi display a cryptic error message.

 

Note also that the return result of LoadDLL is an indication of whether the DLL was loaded successfully. If it was, ExitProc is set up to free the DLL when the application ends (or the component is destroyed).

 

16- and 32-bit Resource Files

The only difference between 16- and 32-bit resource files is the signature block. The format of the individual standard resources has not changed from 16- to 32-bit environments. This means that when all the appropriate resources are bound together into one resource file, the difference is slight. However, the difference is enough to require every resource file built in 16-bit Windows to be rebuilt for access in 32-bit Windows. The easiest way to keep these files portable is to save the 16-bit .RES file as an RC script, while saving all the standard resources in their native file format.

 

An RC file is nothing more than an ASCII file with a specific format. RC files are used by resource compilers to identify which resources to include, and where to find them. In its simplest form, the format of an RC file is as follows:

 

nameID ResourceType filename

 

Some of the basic ResourceTypes available for this format are: BITMAP, CURSOR, and ICON. A sample RC script would look like this:

 

TCLICKLABEL BITMAP "CLKLABEL.BMP"

 

This would assign the bitmap located in the file CLKLABEL.BMP to a bitmap resource type, and would be identified with the resource name TCLICKLABEL. Because the RC script and bitmap file format are both accessible in 16- and 32-bit Windows, the only thing that must be done is compile the resource file to a .RES format for the appropriate environment.

 

Both Delphi 1 and 2 contain a resource compiler (written by Borland) that will create resource files from RC script files for both environments. The original Microsoft Windows Resource Compiler for 16- and 32-bit environments can be obtained from any number of Microsoft Developer resources.

 

To compile an RC script to a 16-bit resource file, make sure \Delphi\Bin is in the path, and type:

 

BRC -R -FOFILENAME.R16 FILENAME.RC

 

32-bit resource files are created in a similar fashion. Make sure the Delphi 2 binary directory is in the path, and type:

 

BRC32 -R -FOFILENAME.R32 FILENAME.RC

 

The 32-bit version also has the capability to create 16-bit resource files. This can be accomplished by adding a -31 flag (for Windows 3.1) to the previous command.

 

BRC32 -R -31 -FOFILENAME.R16 FILENAME.RC

 

For more information on RC files, including a complete description of resource types available, see “RC (Resource Statements)” in the online Help in WINAPI.HLP.

 

Now that the resource files are created, it’s time to put them to use.

 

16- and 32-bit Component Portability

All well written components should have their own icon to display on the Component palette. This helps to readily identify a component. If this file cannot be found, default icons will be used to identify the component. While this may be fine for extremely limited production, it is of critical importance to distinguish components — both in name and icon — from all other components. Imagine how difficult it would be to use the Component palette if everyone decided to not create custom icons.

 

Delphi has a clearly documented system for registering components. For components that need to be installed in the component library, Delphi finds the Register procedure in that unit to determine which component should be installed, and to which Component palette page it should be added. Next, Delphi looks for a .DCR file with the same file name as the registration unit. This .DCR file is simply a resource file that contains a bitmap. This bitmap will be placed on the Component palette to represent the newly registered component. If Delphi finds the .DCR file, it binds that resource file implicitly into COMPLIB.DCL.

 

After Delphi has linked the resource file into the component library, it looks for a bitmap resource with the same name as the installed component’s class name. For example, if a component named TClickLabel is being installed, the bitmap resource must be named TCLICKLABEL for Delphi to attach the bitmap to the component. In Delphi 1, it’s necessary to name this bitmap resource using all capital letters. This is in direct contradiction to the documentation, but it is necessary. If Delphi doesn’t find this resource, it assigns the component’s parent’s bitmap to represent the component on the Component palette.

 

This information has led many component authors (who wish to provide a 16- and 32-bit solution) to adopt the file organization represented in Figure 3. However, this scheme severely limits unit file names. As you can see, file names can be a maximum of 6 characters plus the 16/32 designation to keep the files easily documented. In the finite world of 8.3 file names, it is hard enough to come up with a meaningful and relevant unit file name. Add to this the population of existing and future Delphi components, and the chances of a unit name collision increase dramatically.

 


Figure 3: File organization for automatic component installation.

 

Borland provides a mechanism to deal with the minutiae of component installation. It works well for most cases; however, in true Borland tradition, if their way doesn’t work for all cases, their architecture is open enough to allow anyone to change it. By implementing the file organization shown in Figure 4, Delphi can share a component between the two environments with minimal work. Figure 5 is a typical component registration unit.

 


Figure 4: File organization for optimized component installation.

 

unit CompReg;

 

interface

 

procedure Register;

 

implementation

 

uses

  Classes, ClkLabel;

 

{$IFDEF WIN32}

  {$R *.R32}

{$ELSE}

  {$R *.R16}

{$ENDIF}

 

procedure Register;

begin

  RegisterComponents('DMC', [TClickLabel]);

end;

 

end.

Figure 5: A typical registration unit for a component.

 

Notice in Figure 4 that we did not name our resource files with the .DCR extension. Therefore, Delphi cannot implicitly link the resource file, and should assign a default icon. However, the conditional directive {$IFDEF WIN32}, combined with the include resource file directive {$R}, will bind the appropriate resource file to COMPLIB.DCL explicitly. Delphi doesn’t care how the component’s bitmap was placed in COMPLIB.DCL; it only cares about finding a bitmap resource with the same name as the component.

 

There’s an additional benefit to placing the Component’s palette bitmap in a non-DCR resource file. All other resources for this component can now be placed in that resource file. This will centralize all resources, making it easier to manage. For example, if a custom cursor was also required for a component, it could be placed in the same file as the component bitmap by simply adding the appropriate line to the RC file.

 

Conclusion

This article has shown several pitfalls to avoid during the intricate process of component creation. In addition, tips on how to automatically share a component between 16- and 32-bit Windows were given. By following these simple steps, Delphi programmers everywhere can start to realize the potential of Delphi’s most powerful force — well written components.

 

Dan Miser is a Senior Software Engineer at COMPS InfoSystems in San Diego. He recently spoke about the new features of Delphi 3 at the Borland Conference in Nashville. He is also a Borland Certified Delphi Client/Server Developer. You can contact him at http://www.iinet.com/users/dmiser, or mailto:dmiser@compuserve.com.

 

We’d just as soon avoid the possible pitfalls of the intricate process of component creation. To this end, Mr Miser offers these VCL component construction guidelines.

 

VCL component construction guidelines.