Skip over navigation

How to programatically detect installed versions of Delphi

Contents

Introduction

If you're wanting to execute Delphi or, more likely, its command line compiler from another application, you need a way of finding which Delphi versions are installed. You also need to know where the desired executables have been installed.

The information we need to detect Delphi installations and to execute the compiler is recorded in the registry.

We will take this in two stages. First we will enumerate all installed versions of Delphi and then we will find the full path to an available command line compiler.

Actually executing the compiler from within your program, in a meaningful way, is beyond the scope of this article. If you want another article about how to do that, create an issue and ask!

In the meatime, you can see some code that does this in my CodeSnip program source. Look for units named Compilers.xxx.pas

Note for users of older Delphi compilers: The example code in this article was written and tested in Delphi 11 Alexandria. The code uses features of the later Delphi compilers and will not compile on older version without change. In the main you may need to move all variable declarations out of the body of the code into a var section at the top of each routine. The code also uses for .. in loops quite extensively.

Find valid Delphi installations

Enumerate registry keys

First things first. Lets see where Delphi install information is stored in the registry. That depends on whether Delphi / RAD Studio was installed just for the current user or for all users of the machine.

When installed for all users the information we want is under the HKEY_LOCAL_MACHINE root key, but if installation was for only the current user we need to look in HKEY_CURRENT_USER.

The path to the required data differs for each version of Delphi, but is the same for each root key. The format of the path has changed over the years as Delphi has evolved. The following table gives the registry paths for each version from Delphi 2 to Delphi 11 Alexandria:

Delphi version Registry path
2 \SOFTWARE\Borland\Delphi\2.0
3 \SOFTWARE\Borland\Delphi\3.0
4 \SOFTWARE\Borland\Delphi\4.0
5 \SOFTWARE\Borland\Delphi\5.0
6 \SOFTWARE\Borland\Delphi\6.0
7 \SOFTWARE\Borland\Delphi\7.0
2005 \SOFTWARE\Borland\BDS\3.0
2006 \SOFTWARE\Borland\BDS\4.0
2007 \SOFTWARE\Borland\BDS\5.0
2009 \SOFTWARE\CodeGear\BDS\6.0
2010 \SOFTWARE\CodeGear\BDS\7.0
XE \Software\Embarcadero\BDS\8.0
XE2 \Software\Embarcadero\BDS\9.0
XE3 \Software\Embarcadero\BDS\10.0
XE4 \Software\Embarcadero\BDS\11.0
XE5 \Software\Embarcadero\BDS\12.0
XE6 \Software\Embarcadero\BDS\14.0
XE7 \Software\Embarcadero\BDS\15.0
XE8 \Software\Embarcadero\BDS\16.0
10 Seattle \Software\Embarcadero\BDS\17.0
10.1 Berlin \Software\Embarcadero\BDS\18.0
10.2 Tokyo \Software\Embarcadero\BDS\19.0
10.3 Rio \Software\Embarcadero\BDS\20.0
10.4 Sydney \Software\Embarcadero\BDS\21.0
11 Alexandria \Software\Embarcadero\BDS\22.0

This tabulation suggests a way of enumerating the installed versions of Delphi. We could use an array of records, with each record storing the Delphi version and registry path. We would then enumerate that array, checking whether each registry path exists and recording the indices of those that do exist. In code this would look something like this:

  1uses
  2  System.Win.Registry;
  3 
  4type
  5  TDelphiInfo = record
  6    Name: string;
  7    RegKey: string;
  8  end;
  9 
 10const
 11  AllDelphis: array[0..24] of TDelphiInfo = (
 12    (Name: '2'; RegKey: '\SOFTWARE\Borland\Delphi\2.0'),
 13    (Name: '3'; RegKey: '\SOFTWARE\Borland\Delphi\3.0'),
 14    (Name: '4'; RegKey: '\SOFTWARE\Borland\Delphi\4.0'),
 15    (Name: '5'; RegKey: '\SOFTWARE\Borland\Delphi\5.0'),
 16    (Name: '6'; RegKey: '\SOFTWARE\Borland\Delphi\6.0'),
 17    (Name: '7'; RegKey: '\SOFTWARE\Borland\Delphi\7.0'),
 18    (Name: '2005'; RegKey: '\SOFTWARE\Borland\BDS\3.0'),
 19    (Name: '2006'; RegKey: '\SOFTWARE\Borland\BDS\4.0'),
 20    (Name: '2007'; RegKey: '\SOFTWARE\Borland\BDS\5.0'),
 21    (Name: '2009'; RegKey: '\SOFTWARE\CodeGear\BDS\6.0'),
 22    (Name: '2010'; RegKey: '\SOFTWARE\CodeGear\BDS\7.0'),
 23    (Name: 'XE'; RegKey: '\Software\Embarcadero\BDS\8.0'),
 24    (Name: 'XE2'; RegKey: '\Software\Embarcadero\BDS\9.0'),
 25    (Name: 'XE3'; RegKey: '\Software\Embarcadero\BDS\10.0'),
 26    (Name: 'XE4'; RegKey: '\Software\Embarcadero\BDS\11.0'),
 27    (Name: 'XE5'; RegKey: '\Software\Embarcadero\BDS\12.0'),
 28    (Name: 'XE6'; RegKey: '\Software\Embarcadero\BDS\14.0'),
 29    (Name: 'XE7'; RegKey: '\Software\Embarcadero\BDS\15.0'),
 30    (Name: 'XE8'; RegKey: '\Software\Embarcadero\BDS\16.0'),
 31    (Name: '10 Seattle'; RegKey: '\Software\Embarcadero\BDS\17.0'),
 32    (Name: '10.1 Berlin'; RegKey: '\Software\Embarcadero\BDS\18.0'),
 33    (Name: '10.2 Tokyo'; RegKey: '\Software\Embarcadero\BDS\19.0'),
 34    (Name: '10.3 Rio'; RegKey: '\Software\Embarcadero\BDS\20.0'),
 35    (Name: '10.4 Sydney'; RegKey: '\Software\Embarcadero\BDS\21.0'),
 36    (Name: '11 Alexandria'; RegKey: '\Software\Embarcadero\BDS\22.0')
 37  );
 38 
 39function IsRegisteredInRootKey(RootKey: HKEY; SubKey: string): Boolean;
 40begin
 41  var Reg: TRegistry := TRegistry.Create(KEY_READ);
 42  try
 43    Reg.RootKey := RootKey;
 44    Result := Reg.OpenKeyReadOnly(SubKey);
 45    if Result then
 46      Reg.CloseKey;
 47  finally
 48    Reg.Free;
 49  end;
 50end;
 51 
 52function IsRegistered(SubKey: string): Boolean;
 53begin
 54  Result := IsRegisteredInRootKey(HKEY_LOCAL_MACHINE, SubKey);
 55  if not Result then
 56    Result := IsRegisteredInRootKey(HKEY_CURRENT_USER, SubKey);
 57end;
 58 
 59function FindInstallations: TArray<TDelphiInfo>;
 60begin
 61  SetLength(Result, Length(AllDelphis));
 62  var FoundCount: Integer := 0;
 63  for var Rec: TDelphiInfo in AllDelphis do
 64  begin
 65    if IsRegistered(Rec.RegKey) then
 66    begin
 67      Result[FoundCount] := Rec;
 68      Inc(FoundCount);
 69    end;
 70  end;
 71  SetLength(Result, FoundCount);
 72end;
Listing 1

Here's how this works.

  • TDelphiInfo and AllDelphis together form the table of records mentioned above that stores the required registry information for each compiler.
  • FindInstallations checks the registry for every compiler in AllDelphis and returns an array of TDelphiInfo records for each compiler where its installation subkey is found in the registry. FindInstallations uses the IsRegistered helper routine to actually check the registry.
  • IsRegistered checks for the given subkey in both the HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER root keys. This is because Delphi can be installed for all users of a computer or for a specified user. In the former case the installation information is stored under HKEY_LOCAL_MACHINE and in the later case the information is stored under HKEY_CURRENT_USER.
  • IsRegistered calls IsRegisteredInRootKey to check whether the given subkey is found in each root key.

Validate the list

The above code seems to provide us with a list of all installations of Delphi. While that list is correct in theory, there's a fly in the ointment.

Windows uninstallers are infamous for not cleaning up properly, and its entirely possible that an uninstaller would fail to delete the registry entries for a long gone Delphi installation. In this case our code would find the registry key and would report that version as being installed.

So what's the solution?

Within the registry subkey there is a value named RootDir that is present for all versions of Delphi to date. This value stores the full path of the directory where a given version of Delphi is installed. So, for our first test, we can make sure that the RootDir value exists and that the directory it points to also exists.

The following code reads the installation directory from the registry:

  1uses
  2  System.IOUtils,
  3  System.Win.Registry;
  4 
  5function GetRegRootKey(SubKey: string): HKEY;
  6begin
  7  if IsRegisteredInRootKey(HKEY_LOCAL_MACHINE, SubKey) then
  8    Result := HKEY_LOCAL_MACHINE
  9  else if IsRegisteredInRootKey(HKEY_CURRENT_USER, SubKey) then
 10    Result := HKEY_CURRENT_USER
 11  else
 12    Result := 0;  // not registered
 13end;
 14 
 15function GetInstallDir(SubKey: string): string;
 16begin
 17  var RootKey: HKEY := GetRegRootKey(SubKey);
 18  if RootKey = 0 then
 19    Exit('');
 20  var Reg: TRegistry := TRegistry.Create(KEY_READ);
 21  try
 22    if not Reg.OpenKeyReadOnly(SubKey) then
 23      Exit('');
 24    Result := Reg.ReadString('RootDir');
 25    Reg.CloseKey;
 26  finally
 27    Reg.Free;
 28  end;
Listing 2

Here, GetInstallDir attempts to read the RootDir value from the given subkey. Since we didn't record which root key the subkey belongs to earlier, we need to find it again. The GetRegRootKey helper routine does this.

Next we check if the installation directory exists with the following routine.

  1function InstallDirExists(SubKey: string): Boolean;
  2begin
  3  var InstDir: string := GetInstallDir(SubKey);
  4  if InstDir = '' then
  5    Exit(False);
  6  Result := TDirectory.Exists(InstDir);
  7end;
Listing 3

InstallDirExists simply calls GetInstallDir, checks that a value has been returned from that call, then checks if the directory exists.

We can now write a new version of FindInstallations, FindInstallations2, that checks that the installation directory exists:

  1function FindInstallations2: TArray<TDelphiInfo>;
  2begin
  3  SetLength(Result, Length(AllDelphis));
  4  var FoundCount: Integer := 0;
  5  for var Rec: TDelphiInfo in AllDelphis do
  6  begin
  7    if IsRegistered(Rec.RegKey) and InstallDirExists(Rec.RegKey) then
  8    begin
  9      Result[FoundCount] := Rec;
 10      Inc(FoundCount);
 11    end;
 12  end;
 13  SetLength(Result, FoundCount);
 14end;
Listing 4

FindInstallations2 varies from FindInstallations in the if statement on line 7 where we add a call to InstallDirExists.

We can go a little further and check for the presence of a known file within the Delphi installation. For example we can check if DCC32.exe exists. This file is present in all full versions of Delphi to date, so it's a good choice. DCC32.exe is always located in the Bin sub-directory of the installation directory.

Here's a function to detect if DCC32.exe exists for a given registry subkey:

  1function DCC32Exists(SubKey: string): Boolean;
  2begin
  3  if not InstallDirExists(SubKey) then
  4    Exit(False);
  5  var FileName: string := IncludeTrailingPathDelimiter(GetInstallDir(SubKey))
  6    + 'Bin' + PathDelim + 'DCC32.exe';
  7  Result := TFile.Exists(FileName);
  8end;
Listing 5

We can now extend FindInstallations one last time to check for the presence of DCC32.exe:

  1function FindInstallations3: TArray<TDelphiInfo>;
  2begin
  3  SetLength(Result, Length(AllDelphis));
  4  var FoundCount: Integer := 0;
  5  for var Rec: TDelphiInfo in AllDelphis do
  6  begin
  7    if IsRegistered(Rec.RegKey) and DCC32Exists(Rec.RegKey) then
  8    begin
  9      Result[FoundCount] := Rec;
 10      Inc(FoundCount);
 11    end;
 12  end;
 13  SetLength(Result, FoundCount);
 14end;
Listing 6

Again, the only difference from FindInstallations is in line 7 with its call to DCC32Exists.

Let us end this section with a little VCL program that displays the names of all the Delphi installations available to the current user of a PC.

Create a new VCL application and drop a TListBox on the form. Add all the functions above to the implementation section of the form unit. Now add an OnCreate event handler to the form and code it as follows:

  1procedure TForm1.FormCreate(Sender: TObject);
  2begin
  3  for var Rec: TDelphiInfo in FindInstallations3 do
  4    ListBox1.Items.Add(Rec.Name);
  5end;
Listing 7

The code assumes that the default form and list box names were left unchanged.

This code gets a TDelphiInfo record for each installed version of Delphi and adds its name to the list box. Here's what I got:

Program window listing names of installed versions of Delphi
Image 1

Find the command line compilers

This is the easiest step by far, and the solution has already been hinted at above where we tested for a valid install by looking for DCC32.exe, the 32 bit command line compiler. That code found the full path we were looking for.

What about the 64 bit compiler? We simply use the same technique as used above to check for the presence of DCC64.exe. Not all versions of Delphi have a 64 bit compiler, but we can use this code to find out which versions do support it.

The following code displays the locations of the 32bit and 64bit compilers (where available). This code uses the same project as before, with a main form containing a list box. We change the OnCreate event handler

  1function TryGetFilePath(BaseName, SubKey: string; out Path: string): Boolean;
  2begin
  3  Path := '';
  4  if not InstallDirExists(SubKey) then
  5    Exit(False);
  6  Path := IncludeTrailingPathDelimiter(GetInstallDir(SubKey))
  7    + 'Bin' + PathDelim + BaseName;
  8  Result := TFile.Exists(Path);
  9end;
 10
 11procedure TForm1.FormCreate(Sender: TObject);
 12const
 13  DCC32FN = 'DCC32.exe';
 14  DCC64FN = 'DCC64.exe';
 15
 16  procedure AddDCCInfo(DCC: string; Rec: TDelphiInfo);
 17  begin
 18    var Path: string;
 19    var Installed: Boolean := TryGetFilePath(DCC, Rec.RegKey, Path);
 20    if Installed then
 21      ListBox1.Items.Add('   ' + DCC + ' installed at: ' + Path)
 22    else
 23      ListBox1.Items.Add('   ' + DCC + ' not available');
 24  end;
 25
 26begin
 27  var Installations: TArray<TDelphiInfo> := FindInstallations2;
 28  for var Rec: TDelphiInfo in Installations do
 29  begin
 30    ListBox1.Items.Add('Delphi ' + Rec.Name);
 31    AddDCCInfo(DCC32FN, Rec);
 32    AddDCCInfo(DCC64FN, Rec);
 33  end;
 34end;
Listing 8

Firstly, we have yet another helper routine, TryGetFilePath, that tries to get the full path of given file in a specified Delphi installation. BaseName is the name of the required file, without any path informaton; SubKey is the registry sub key that provides information about the Delphi installation and Path receives the fully specified path to the required file. TryGetFilePath returns True if the file exists and False if not. Where the file doesn't exists Path is set to ''.

In the TForm1.FormCreate event handler we first get a list of all installed versions of Delphi. The name of each version is added to the list box. We then check to see if DCC32.exe and DCC64.exe are included in the installation, using the local procedure AddDCCInfo. This procedure checks if the required programs exist and writes the full path to the executable file if so.

Here are the results of running the code on my system:

Program window listing which of DCC32.exe and DCC64.exe are available for each installed version of Delphi
Image 2

You now have all the information you need to be able to find various Delphi executable programs.

You could use a similar technique to find other compilers, like the C++ compiler providing you know the executable file name. I don't!